├── .cargo └── config.toml ├── .github └── workflows │ ├── rust.yml │ └── website.yml ├── .gitignore ├── .typos.toml ├── CHANGELOG.md ├── CONTRIBUTING.md ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── build.rs ├── clippy.toml ├── dev-Cargo.toml ├── dev ├── Cargo.toml └── rustlings-repo.txt ├── exercises ├── 00_intro │ ├── README.md │ ├── intro1.rs │ └── intro2.rs ├── 01_variables │ ├── README.md │ ├── variables1.rs │ ├── variables2.rs │ ├── variables3.rs │ ├── variables4.rs │ ├── variables5.rs │ └── variables6.rs ├── 02_functions │ ├── README.md │ ├── functions1.rs │ ├── functions2.rs │ ├── functions3.rs │ ├── functions4.rs │ └── functions5.rs ├── 03_if │ ├── README.md │ ├── if1.rs │ ├── if2.rs │ └── if3.rs ├── 04_primitive_types │ ├── README.md │ ├── primitive_types1.rs │ ├── primitive_types2.rs │ ├── primitive_types3.rs │ ├── primitive_types4.rs │ ├── primitive_types5.rs │ └── primitive_types6.rs ├── 05_vecs │ ├── README.md │ ├── vecs1.rs │ └── vecs2.rs ├── 06_move_semantics │ ├── README.md │ ├── move_semantics1.rs │ ├── move_semantics2.rs │ ├── move_semantics3.rs │ ├── move_semantics4.rs │ └── move_semantics5.rs ├── 07_structs │ ├── README.md │ ├── structs1.rs │ ├── structs2.rs │ └── structs3.rs ├── 08_enums │ ├── README.md │ ├── enums1.rs │ ├── enums2.rs │ └── enums3.rs ├── 09_strings │ ├── README.md │ ├── strings1.rs │ ├── strings2.rs │ ├── strings3.rs │ └── strings4.rs ├── 10_modules │ ├── README.md │ ├── modules1.rs │ ├── modules2.rs │ └── modules3.rs ├── 11_hashmaps │ ├── README.md │ ├── hashmaps1.rs │ ├── hashmaps2.rs │ └── hashmaps3.rs ├── 12_options │ ├── README.md │ ├── options1.rs │ ├── options2.rs │ └── options3.rs ├── 13_error_handling │ ├── README.md │ ├── errors1.rs │ ├── errors2.rs │ ├── errors3.rs │ ├── errors4.rs │ ├── errors5.rs │ └── errors6.rs ├── 14_generics │ ├── README.md │ ├── generics1.rs │ └── generics2.rs ├── 15_traits │ ├── README.md │ ├── traits1.rs │ ├── traits2.rs │ ├── traits3.rs │ ├── traits4.rs │ └── traits5.rs ├── 16_lifetimes │ ├── README.md │ ├── lifetimes1.rs │ ├── lifetimes2.rs │ └── lifetimes3.rs ├── 17_tests │ ├── README.md │ ├── tests1.rs │ ├── tests2.rs │ └── tests3.rs ├── 18_iterators │ ├── README.md │ ├── iterators1.rs │ ├── iterators2.rs │ ├── iterators3.rs │ ├── iterators4.rs │ └── iterators5.rs ├── 19_smart_pointers │ ├── README.md │ ├── arc1.rs │ ├── box1.rs │ ├── cow1.rs │ └── rc1.rs ├── 20_threads │ ├── README.md │ ├── threads1.rs │ ├── threads2.rs │ └── threads3.rs ├── 21_macros │ ├── README.md │ ├── macros1.rs │ ├── macros2.rs │ ├── macros3.rs │ └── macros4.rs ├── 22_clippy │ ├── README.md │ ├── clippy1.rs │ ├── clippy2.rs │ └── clippy3.rs ├── 23_conversions │ ├── README.md │ ├── as_ref_mut.rs │ ├── from_into.rs │ ├── from_str.rs │ ├── try_from_into.rs │ └── using_as.rs ├── README.md └── quizzes │ ├── README.md │ ├── quiz1.rs │ ├── quiz2.rs │ └── quiz3.rs ├── release-hook.sh ├── rustlings-macros ├── Cargo.toml ├── info.toml └── src │ └── lib.rs ├── solutions ├── 00_intro │ ├── intro1.rs │ └── intro2.rs ├── 01_variables │ ├── variables1.rs │ ├── variables2.rs │ ├── variables3.rs │ ├── variables4.rs │ ├── variables5.rs │ └── variables6.rs ├── 02_functions │ ├── functions1.rs │ ├── functions2.rs │ ├── functions3.rs │ ├── functions4.rs │ └── functions5.rs ├── 03_if │ ├── if1.rs │ ├── if2.rs │ └── if3.rs ├── 04_primitive_types │ ├── primitive_types1.rs │ ├── primitive_types2.rs │ ├── primitive_types3.rs │ ├── primitive_types4.rs │ ├── primitive_types5.rs │ └── primitive_types6.rs ├── 05_vecs │ ├── vecs1.rs │ └── vecs2.rs ├── 06_move_semantics │ ├── move_semantics1.rs │ ├── move_semantics2.rs │ ├── move_semantics3.rs │ ├── move_semantics4.rs │ └── move_semantics5.rs ├── 07_structs │ ├── structs1.rs │ ├── structs2.rs │ └── structs3.rs ├── 08_enums │ ├── enums1.rs │ ├── enums2.rs │ └── enums3.rs ├── 09_strings │ ├── strings1.rs │ ├── strings2.rs │ ├── strings3.rs │ └── strings4.rs ├── 10_modules │ ├── modules1.rs │ ├── modules2.rs │ └── modules3.rs ├── 11_hashmaps │ ├── hashmaps1.rs │ ├── hashmaps2.rs │ └── hashmaps3.rs ├── 12_options │ ├── options1.rs │ ├── options2.rs │ └── options3.rs ├── 13_error_handling │ ├── errors1.rs │ ├── errors2.rs │ ├── errors3.rs │ ├── errors4.rs │ ├── errors5.rs │ └── errors6.rs ├── 14_generics │ ├── generics1.rs │ └── generics2.rs ├── 15_traits │ ├── traits1.rs │ ├── traits2.rs │ ├── traits3.rs │ ├── traits4.rs │ └── traits5.rs ├── 16_lifetimes │ ├── lifetimes1.rs │ ├── lifetimes2.rs │ └── lifetimes3.rs ├── 17_tests │ ├── tests1.rs │ ├── tests2.rs │ └── tests3.rs ├── 18_iterators │ ├── iterators1.rs │ ├── iterators2.rs │ ├── iterators3.rs │ ├── iterators4.rs │ └── iterators5.rs ├── 19_smart_pointers │ ├── arc1.rs │ ├── box1.rs │ ├── cow1.rs │ └── rc1.rs ├── 20_threads │ ├── threads1.rs │ ├── threads2.rs │ └── threads3.rs ├── 21_macros │ ├── macros1.rs │ ├── macros2.rs │ ├── macros3.rs │ └── macros4.rs ├── 22_clippy │ ├── clippy1.rs │ ├── clippy2.rs │ └── clippy3.rs ├── 23_conversions │ ├── as_ref_mut.rs │ ├── from_into.rs │ ├── from_str.rs │ ├── try_from_into.rs │ └── using_as.rs ├── README.md └── quizzes │ ├── quiz1.rs │ ├── quiz2.rs │ └── quiz3.rs ├── src ├── app_state.rs ├── cargo_toml.rs ├── cmd.rs ├── dev.rs ├── dev │ ├── check.rs │ ├── new.rs │ └── update.rs ├── embedded.rs ├── exercise.rs ├── info_file.rs ├── init.rs ├── list.rs ├── list │ ├── scroll_state.rs │ └── state.rs ├── main.rs ├── run.rs ├── term.rs ├── watch.rs └── watch │ ├── notify_event.rs │ ├── state.rs │ └── terminal_event.rs ├── tests ├── integration_tests.rs └── test_exercises │ ├── dev │ └── Cargo.toml │ ├── exercises │ ├── compilation_failure.rs │ ├── compilation_success.rs │ ├── not_in_info.rs │ ├── test_failure.rs │ └── test_success.rs │ └── info.toml └── website ├── .gitignore ├── config.toml ├── content ├── _index.md ├── community-exercises │ └── index.md ├── setup │ └── index.md └── usage │ └── index.md ├── input.css ├── justfile ├── package.json ├── static └── images │ ├── happy_ferris.svg │ ├── panic.svg │ └── rust_logo.svg └── templates ├── 404.html ├── anchor-link.html ├── base.html ├── index.html ├── page.html └── shortcodes └── details.html /.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [alias] 2 | dev = ["run", "--", "dev"] 3 | -------------------------------------------------------------------------------- /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Check 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | paths-ignore: 7 | - website 8 | - '*.md' 9 | pull_request: 10 | branches: [main] 11 | paths-ignore: 12 | - website 13 | - '*.md' 14 | 15 | env: 16 | CARGO_TERM_COLOR: always 17 | 18 | jobs: 19 | clippy: 20 | runs-on: ubuntu-latest 21 | steps: 22 | - uses: actions/checkout@v4 23 | - name: Clippy 24 | run: cargo clippy -- --deny warnings 25 | fmt: 26 | runs-on: ubuntu-latest 27 | steps: 28 | - uses: actions/checkout@v4 29 | - name: rustfmt 30 | run: cargo fmt --all --check 31 | test: 32 | runs-on: ${{ matrix.os }} 33 | strategy: 34 | matrix: 35 | os: [ubuntu-latest, windows-latest, macos-latest] 36 | steps: 37 | - uses: actions/checkout@v4 38 | - uses: swatinem/rust-cache@v2 39 | - name: cargo test 40 | run: cargo test --workspace 41 | dev-check: 42 | runs-on: ubuntu-latest 43 | steps: 44 | - uses: actions/checkout@v4 45 | - uses: swatinem/rust-cache@v2 46 | - name: rustlings dev check 47 | run: cargo dev check --require-solutions 48 | -------------------------------------------------------------------------------- /.github/workflows/website.yml: -------------------------------------------------------------------------------- 1 | name: Website 2 | 3 | on: 4 | workflow_dispatch: 5 | push: 6 | branches: [main] 7 | paths: [website] 8 | 9 | jobs: 10 | build: 11 | defaults: 12 | run: 13 | working-directory: website 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@v4 17 | - name: Install TailwindCSS 18 | run: npm install 19 | - name: Build CSS 20 | run: npx @tailwindcss/cli -m -i input.css -o static/main.css 21 | - name: Download Zola 22 | run: curl -fsSL https://github.com/getzola/zola/releases/download/v0.20.0/zola-v0.20.0-x86_64-unknown-linux-gnu.tar.gz | tar xz 23 | - name: Build site 24 | run: ./zola build 25 | - name: Upload static files as artifact 26 | uses: actions/upload-pages-artifact@v3 27 | with: 28 | path: website/public/ 29 | 30 | deploy: 31 | needs: build 32 | # Grant GITHUB_TOKEN the permissions required to make a Pages deployment 33 | permissions: 34 | pages: write # to deploy to Pages 35 | id-token: write # to verify the deployment originates from an appropriate source 36 | # Deploy to the github-pages environment 37 | environment: 38 | name: github-pages 39 | url: ${{ steps.deployment.outputs.page_url }} 40 | runs-on: ubuntu-latest 41 | steps: 42 | - name: Deploy to GitHub Pages 43 | uses: actions/deploy-pages@v4 44 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Cargo 2 | target/ 3 | Cargo.lock 4 | !/Cargo.lock 5 | 6 | # State file 7 | .rustlings-state.txt 8 | 9 | # OS 10 | .DS_Store 11 | .direnv/ 12 | 13 | # Editor 14 | *.swp 15 | .idea 16 | *.iml 17 | 18 | # Ignore file for editors like Helix 19 | .ignore 20 | -------------------------------------------------------------------------------- /.typos.toml: -------------------------------------------------------------------------------- 1 | [default.extend-words] 2 | "earch" = "earch" # Because of earch in the list footer 3 | 4 | [files] 5 | extend-exclude = [ 6 | "CHANGELOG.md", 7 | ] 8 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to Rustlings 2 | 3 | First off, thanks for taking the time to contribute! ❤️ 4 | 5 | ## Quick Reference 6 | 7 | I want to … 8 | 9 | - _report a bug!_ ➡️ [open an issue](#issues) 10 | - _fix a bug!_ ➡️ [open a pull request](#pull-requests) 11 | - _implement a new feature!_ ➡️ [open an issue to discuss it first, then a pull request](#issues) 12 | - _add an exercise!_ ➡️ [read this](#adding-an-exercise) 13 | - _update an outdated exercise!_ ➡️ [open a pull request](#pull-requests) 14 | 15 | ## Issues 16 | 17 | You can open an issue [here](https://github.com/rust-lang/rustlings/issues/new). 18 | If you're reporting a bug, please include the output of the following commands: 19 | 20 | - `cargo --version` 21 | - `rustlings --version` 22 | - `ls -la` 23 | - Your OS name and version 24 | 25 | ## Pull Requests 26 | 27 | You are welcome to open a pull request, but unless it is small and trivial, **please open an issue to discuss your idea first** 🙏🏼 28 | 29 | Opening a pull request is as easy as forking the repository and committing your changes. 30 | If you need any help with it or face any Git related problems, don't hesitate to ask for help 🤗 31 | 32 | It may take time to review your pull request. 33 | Please be patient 😇 34 | 35 | When updating an exercise, check if its solution needs to be updated. 36 | 37 | ## Adding An Exercise 38 | 39 | - Name the file `exercises/yourTopic/yourTopicN.rs`. 40 | - Make sure to put in some helpful links, and link to sections of The Book in `exercises/yourTopic/README.md`. 41 | - In the exercise, add a `// TODO: …` comment where user changes are required. 42 | - Add a solution at `solutions/yourTopic/yourTopicN.rs` with comments explaining it. 43 | - Add the [metadata for your exercise](#exercise-metadata) in the `rustlings-macros/info.toml` file. 44 | - Make sure your exercise runs with `rustlings run yourTopicN`. 45 | - [Open a pull request](#pull-requests). 46 | 47 | ### Exercise Metadata 48 | 49 | The exercise metadata should contain the following: 50 | 51 | ```toml 52 | [[exercises]] 53 | name = "yourTopicN" 54 | dir = "yourTopic" 55 | hint = """ 56 | A useful (multi-line) hint for your exercise. 57 | Include links to a section in The Book or a documentation page.""" 58 | ``` 59 | 60 | If your exercise doesn't contain any test, add `test = false` to the exercise metadata. 61 | But adding tests is recommended. 62 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | resolver = "2" 3 | exclude = [ 4 | "tests/test_exercises", 5 | "dev", 6 | ] 7 | 8 | [workspace.package] 9 | version = "6.4.0" 10 | authors = [ 11 | "Mo Bitar ", # https://github.com/mo8it 12 | "Liv ", # https://github.com/shadows-withal 13 | # Alumni 14 | "Carol (Nichols || Goulding) ", # https://github.com/carols10cents 15 | ] 16 | repository = "https://github.com/rust-lang/rustlings" 17 | license = "MIT" 18 | edition = "2024" # On Update: Update the edition of `rustfmt` in `dev check` and `CARGO_TOML` in `dev new`. 19 | rust-version = "1.87" 20 | 21 | [workspace.dependencies] 22 | serde = { version = "1.0", features = ["derive"] } 23 | toml_edit = { version = "0.22", default-features = false, features = ["parse", "serde"] } 24 | 25 | [package] 26 | name = "rustlings" 27 | description = "Small exercises to get you used to reading and writing Rust code!" 28 | version.workspace = true 29 | authors.workspace = true 30 | repository.workspace = true 31 | license.workspace = true 32 | edition.workspace = true 33 | rust-version.workspace = true 34 | keywords = [ 35 | "exercise", 36 | "learning", 37 | ] 38 | include = [ 39 | "/src/", 40 | "/exercises/", 41 | "/solutions/", 42 | # A symlink to be able to include `dev/Cargo.toml` although `dev` is excluded. 43 | "/dev-Cargo.toml", 44 | "/README.md", 45 | "/LICENSE", 46 | ] 47 | 48 | [dependencies] 49 | anyhow = "1.0" 50 | clap = { version = "4.5", features = ["derive"] } 51 | crossterm = { version = "0.29", default-features = false, features = ["windows", "events"] } 52 | notify = "8.0" 53 | rustlings-macros = { path = "rustlings-macros", version = "=6.4.0" } 54 | serde_json = "1.0" 55 | serde.workspace = true 56 | toml_edit.workspace = true 57 | 58 | [target.'cfg(not(windows))'.dependencies] 59 | rustix = { version = "1.0", default-features = false, features = ["std", "stdio", "termios"] } 60 | 61 | [dev-dependencies] 62 | tempfile = "3.19" 63 | 64 | [profile.release] 65 | panic = "abort" 66 | 67 | [profile.dev] 68 | panic = "abort" 69 | 70 | [package.metadata.release] 71 | pre-release-hook = ["./release-hook.sh"] 72 | pre-release-commit-message = "Release 🎉" 73 | 74 | [workspace.lints.rust] 75 | unsafe_code = "forbid" 76 | unstable_features = "forbid" 77 | 78 | [workspace.lints.clippy] 79 | empty_loop = "forbid" 80 | disallowed-types = "deny" 81 | disallowed-methods = "deny" 82 | infinite_loop = "deny" 83 | mem_forget = "deny" 84 | dbg_macro = "warn" 85 | todo = "warn" 86 | 87 | [lints] 88 | workspace = true 89 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Carol (Nichols || Goulding) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # [Rustlings](https://rustlings.rust-lang.org) 🦀 2 | 3 | Small exercises to get you used to reading and writing [Rust](https://www.rust-lang.org) code - _Recommended in parallel to reading [the official Rust book](https://doc.rust-lang.org/book) 📚️_ 4 | 5 | Visit the **website** for a demo, info about setup and more: 6 | 7 | ## ➡️ [rustlings.rust-lang.org](https://rustlings.rust-lang.org) ⬅️ 8 | -------------------------------------------------------------------------------- /build.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | // Fix building from source on Windows because it can't handle file links. 3 | #[cfg(windows)] 4 | let _ = std::fs::copy("dev/Cargo.toml", "dev-Cargo.toml"); 5 | } 6 | -------------------------------------------------------------------------------- /clippy.toml: -------------------------------------------------------------------------------- 1 | disallowed-types = [ 2 | # Inefficient. Use `.queue(…)` instead. 3 | "crossterm::style::Stylize", 4 | "crossterm::style::styled_content::StyledContent", 5 | ] 6 | 7 | disallowed-methods = [ 8 | # Inefficient. Use `.queue(…)` instead. 9 | "crossterm::style::style", 10 | # Use `thread::Builder::spawn` instead and handle the error. 11 | "std::thread::spawn", 12 | "std::thread::Scope::spawn", 13 | # Return `ExitCode` instead. 14 | "std::process::exit", 15 | ] 16 | -------------------------------------------------------------------------------- /dev-Cargo.toml: -------------------------------------------------------------------------------- 1 | dev/Cargo.toml -------------------------------------------------------------------------------- /dev/rustlings-repo.txt: -------------------------------------------------------------------------------- 1 | This file is used to check if the user tries to run Rustlings in the repository (the method before version 6) 2 | -------------------------------------------------------------------------------- /exercises/00_intro/README.md: -------------------------------------------------------------------------------- 1 | # Intro 2 | 3 | Rust uses the `print!` and `println!` macros to print text to the console. 4 | 5 | ## Further information 6 | 7 | - [Hello World](https://doc.rust-lang.org/rust-by-example/hello.html) 8 | - [Formatted print](https://doc.rust-lang.org/rust-by-example/hello/print.html) 9 | -------------------------------------------------------------------------------- /exercises/00_intro/intro1.rs: -------------------------------------------------------------------------------- 1 | // TODO: We sometimes encourage you to keep trying things on a given exercise 2 | // even after you already figured it out. If you got everything working and feel 3 | // ready for the next exercise, enter `n` in the terminal. 4 | // 5 | // The exercise file will be reloaded when you change one of the lines below! 6 | // Try adding a new `println!` and check the updated output in the terminal. 7 | 8 | fn main() { 9 | println!(r#" Welcome to... "#); 10 | println!(r#" _ _ _ "#); 11 | println!(r#" _ __ _ _ ___| |_| (_)_ __ __ _ ___ "#); 12 | println!(r#" | '__| | | / __| __| | | '_ \ / _` / __| "#); 13 | println!(r#" | | | |_| \__ \ |_| | | | | | (_| \__ \ "#); 14 | println!(r#" |_| \__,_|___/\__|_|_|_| |_|\__, |___/ "#); 15 | println!(r#" |___/ "#); 16 | println!(); 17 | println!("This exercise compiles successfully. The remaining exercises contain a compiler"); 18 | println!("or logic error. The central concept behind Rustlings is to fix these errors and"); 19 | println!("solve the exercises. Good luck!"); 20 | println!(); 21 | println!("The file of this exercise is `exercises/00_intro/intro1.rs`. Have a look!"); 22 | println!("The current exercise path will be always shown under the progress bar."); 23 | println!("You can click on the path to open the exercise file in your editor."); 24 | } 25 | -------------------------------------------------------------------------------- /exercises/00_intro/intro2.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | // TODO: Fix the code to print "Hello world!". 3 | printline!("Hello world!"); 4 | } 5 | -------------------------------------------------------------------------------- /exercises/01_variables/README.md: -------------------------------------------------------------------------------- 1 | # Variables 2 | 3 | In Rust, variables are immutable by default. 4 | When a variable is immutable, once a value is bound to a name, you can't change that value. 5 | You can make them mutable by adding `mut` in front of the variable name. 6 | 7 | ## Further information 8 | 9 | - [Variables and Mutability](https://doc.rust-lang.org/book/ch03-01-variables-and-mutability.html) 10 | -------------------------------------------------------------------------------- /exercises/01_variables/variables1.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | // TODO: Add the missing keyword. 3 | x = 5; 4 | 5 | println!("x has the value {x}"); 6 | } 7 | -------------------------------------------------------------------------------- /exercises/01_variables/variables2.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | // TODO: Change the line below to fix the compiler error. 3 | let x; 4 | 5 | if x == 10 { 6 | println!("x is ten!"); 7 | } else { 8 | println!("x is not ten!"); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /exercises/01_variables/variables3.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | // TODO: Change the line below to fix the compiler error. 3 | let x: i32; 4 | 5 | println!("Number {x}"); 6 | } 7 | -------------------------------------------------------------------------------- /exercises/01_variables/variables4.rs: -------------------------------------------------------------------------------- 1 | // TODO: Fix the compiler error. 2 | fn main() { 3 | let x = 3; 4 | println!("Number {x}"); 5 | 6 | x = 5; // Don't change this line 7 | println!("Number {x}"); 8 | } 9 | -------------------------------------------------------------------------------- /exercises/01_variables/variables5.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | let number = "T-H-R-E-E"; // Don't change this line 3 | println!("Spell a number: {number}"); 4 | 5 | // TODO: Fix the compiler error by changing the line below without renaming the variable. 6 | number = 3; 7 | println!("Number plus two is: {}", number + 2); 8 | } 9 | -------------------------------------------------------------------------------- /exercises/01_variables/variables6.rs: -------------------------------------------------------------------------------- 1 | // TODO: Change the line below to fix the compiler error. 2 | const NUMBER = 3; 3 | 4 | fn main() { 5 | println!("Number: {NUMBER}"); 6 | } 7 | -------------------------------------------------------------------------------- /exercises/02_functions/README.md: -------------------------------------------------------------------------------- 1 | # Functions 2 | 3 | Here, you'll learn how to write functions and how the Rust compiler can help you debug errors even 4 | in more complex code. 5 | 6 | ## Further information 7 | 8 | - [How Functions Work](https://doc.rust-lang.org/book/ch03-03-how-functions-work.html) 9 | -------------------------------------------------------------------------------- /exercises/02_functions/functions1.rs: -------------------------------------------------------------------------------- 1 | // TODO: Add some function with the name `call_me` without arguments or a return value. 2 | 3 | fn main() { 4 | call_me(); // Don't change this line 5 | } 6 | -------------------------------------------------------------------------------- /exercises/02_functions/functions2.rs: -------------------------------------------------------------------------------- 1 | // TODO: Add the missing type of the argument `num` after the colon `:`. 2 | fn call_me(num:) { 3 | for i in 0..num { 4 | println!("Ring! Call number {}", i + 1); 5 | } 6 | } 7 | 8 | fn main() { 9 | call_me(3); 10 | } 11 | -------------------------------------------------------------------------------- /exercises/02_functions/functions3.rs: -------------------------------------------------------------------------------- 1 | fn call_me(num: u8) { 2 | for i in 0..num { 3 | println!("Ring! Call number {}", i + 1); 4 | } 5 | } 6 | 7 | fn main() { 8 | // TODO: Fix the function call. 9 | call_me(); 10 | } 11 | -------------------------------------------------------------------------------- /exercises/02_functions/functions4.rs: -------------------------------------------------------------------------------- 1 | // This store is having a sale where if the price is an even number, you get 10 2 | // Rustbucks off, but if it's an odd number, it's 3 Rustbucks off. 3 | // Don't worry about the function bodies themselves, we are only interested in 4 | // the signatures for now. 5 | 6 | fn is_even(num: i64) -> bool { 7 | num % 2 == 0 8 | } 9 | 10 | // TODO: Fix the function signature. 11 | fn sale_price(price: i64) -> { 12 | if is_even(price) { 13 | price - 10 14 | } else { 15 | price - 3 16 | } 17 | } 18 | 19 | fn main() { 20 | let original_price = 51; 21 | println!("Your sale price is {}", sale_price(original_price)); 22 | } 23 | -------------------------------------------------------------------------------- /exercises/02_functions/functions5.rs: -------------------------------------------------------------------------------- 1 | // TODO: Fix the function body without changing the signature. 2 | fn square(num: i32) -> i32 { 3 | num * num; 4 | } 5 | 6 | fn main() { 7 | let answer = square(3); 8 | println!("The square of 3 is {answer}"); 9 | } 10 | -------------------------------------------------------------------------------- /exercises/03_if/README.md: -------------------------------------------------------------------------------- 1 | # If 2 | 3 | `if`, the most basic (but still surprisingly versatile!) type of control flow, is what you'll learn here. 4 | 5 | ## Further information 6 | 7 | - [Control Flow - if expressions](https://doc.rust-lang.org/book/ch03-05-control-flow.html#if-expressions) 8 | -------------------------------------------------------------------------------- /exercises/03_if/if1.rs: -------------------------------------------------------------------------------- 1 | fn bigger(a: i32, b: i32) -> i32 { 2 | // TODO: Complete this function to return the bigger number! 3 | // If both numbers are equal, any of them can be returned. 4 | // Do not use: 5 | // - another function call 6 | // - additional variables 7 | } 8 | 9 | fn main() { 10 | // You can optionally experiment here. 11 | } 12 | 13 | // Don't mind this for now :) 14 | #[cfg(test)] 15 | mod tests { 16 | use super::*; 17 | 18 | #[test] 19 | fn ten_is_bigger_than_eight() { 20 | assert_eq!(10, bigger(10, 8)); 21 | } 22 | 23 | #[test] 24 | fn fortytwo_is_bigger_than_thirtytwo() { 25 | assert_eq!(42, bigger(32, 42)); 26 | } 27 | 28 | #[test] 29 | fn equal_numbers() { 30 | assert_eq!(42, bigger(42, 42)); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /exercises/03_if/if2.rs: -------------------------------------------------------------------------------- 1 | // TODO: Fix the compiler error on this function. 2 | fn picky_eater(food: &str) -> &str { 3 | if food == "strawberry" { 4 | "Yummy!" 5 | } else { 6 | 1 7 | } 8 | } 9 | 10 | fn main() { 11 | // You can optionally experiment here. 12 | } 13 | 14 | // TODO: Read the tests to understand the desired behavior. 15 | // Make all tests pass without changing them. 16 | #[cfg(test)] 17 | mod tests { 18 | use super::*; 19 | 20 | #[test] 21 | fn yummy_food() { 22 | // This means that calling `picky_eater` with the argument "strawberry" should return "Yummy!". 23 | assert_eq!(picky_eater("strawberry"), "Yummy!"); 24 | } 25 | 26 | #[test] 27 | fn neutral_food() { 28 | assert_eq!(picky_eater("potato"), "I guess I can eat that."); 29 | } 30 | 31 | #[test] 32 | fn default_disliked_food() { 33 | assert_eq!(picky_eater("broccoli"), "No thanks!"); 34 | assert_eq!(picky_eater("gummy bears"), "No thanks!"); 35 | assert_eq!(picky_eater("literally anything"), "No thanks!"); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /exercises/03_if/if3.rs: -------------------------------------------------------------------------------- 1 | fn animal_habitat(animal: &str) -> &str { 2 | // TODO: Fix the compiler error in the statement below. 3 | let identifier = if animal == "crab" { 4 | 1 5 | } else if animal == "gopher" { 6 | 2.0 7 | } else if animal == "snake" { 8 | 3 9 | } else { 10 | "Unknown" 11 | }; 12 | 13 | // Don't change the expression below! 14 | if identifier == 1 { 15 | "Beach" 16 | } else if identifier == 2 { 17 | "Burrow" 18 | } else if identifier == 3 { 19 | "Desert" 20 | } else { 21 | "Unknown" 22 | } 23 | } 24 | 25 | fn main() { 26 | // You can optionally experiment here. 27 | } 28 | 29 | // Don't change the tests! 30 | #[cfg(test)] 31 | mod tests { 32 | use super::*; 33 | 34 | #[test] 35 | fn gopher_lives_in_burrow() { 36 | assert_eq!(animal_habitat("gopher"), "Burrow") 37 | } 38 | 39 | #[test] 40 | fn snake_lives_in_desert() { 41 | assert_eq!(animal_habitat("snake"), "Desert") 42 | } 43 | 44 | #[test] 45 | fn crab_lives_on_beach() { 46 | assert_eq!(animal_habitat("crab"), "Beach") 47 | } 48 | 49 | #[test] 50 | fn unknown_animal() { 51 | assert_eq!(animal_habitat("dinosaur"), "Unknown") 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /exercises/04_primitive_types/README.md: -------------------------------------------------------------------------------- 1 | # Primitive Types 2 | 3 | Rust has a couple of basic types that are directly implemented into the 4 | compiler. In this section, we'll go through the most important ones. 5 | 6 | ## Further information 7 | 8 | - [Data Types](https://doc.rust-lang.org/book/ch03-02-data-types.html) 9 | - [The Slice Type](https://doc.rust-lang.org/book/ch04-03-slices.html) 10 | -------------------------------------------------------------------------------- /exercises/04_primitive_types/primitive_types1.rs: -------------------------------------------------------------------------------- 1 | // Booleans (`bool`) 2 | 3 | fn main() { 4 | let is_morning = true; 5 | if is_morning { 6 | println!("Good morning!"); 7 | } 8 | 9 | // TODO: Define a boolean variable with the name `is_evening` before the `if` statement below. 10 | // The value of the variable should be the negation (opposite) of `is_morning`. 11 | // let … 12 | if is_evening { 13 | println!("Good evening!"); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /exercises/04_primitive_types/primitive_types2.rs: -------------------------------------------------------------------------------- 1 | // Characters (`char`) 2 | 3 | fn main() { 4 | // Note the _single_ quotes, these are different from the double quotes 5 | // you've been seeing around. 6 | let my_first_initial = 'C'; 7 | if my_first_initial.is_alphabetic() { 8 | println!("Alphabetical!"); 9 | } else if my_first_initial.is_numeric() { 10 | println!("Numerical!"); 11 | } else { 12 | println!("Neither alphabetic nor numeric!"); 13 | } 14 | 15 | // TODO: Analogous to the example before, declare a variable called `your_character` 16 | // below with your favorite character. 17 | // Try a letter, try a digit (in single quotes), try a special character, try a character 18 | // from a different language than your own, try an emoji 😉 19 | // let your_character = ''; 20 | 21 | if your_character.is_alphabetic() { 22 | println!("Alphabetical!"); 23 | } else if your_character.is_numeric() { 24 | println!("Numerical!"); 25 | } else { 26 | println!("Neither alphabetic nor numeric!"); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /exercises/04_primitive_types/primitive_types3.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | // TODO: Create an array called `a` with at least 100 elements in it. 3 | // let a = ??? 4 | 5 | if a.len() >= 100 { 6 | println!("Wow, that's a big array!"); 7 | } else { 8 | println!("Meh, I eat arrays like that for breakfast."); 9 | panic!("Array not big enough, more elements needed"); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /exercises/04_primitive_types/primitive_types4.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | // You can optionally experiment here. 3 | } 4 | 5 | #[cfg(test)] 6 | mod tests { 7 | #[test] 8 | fn slice_out_of_array() { 9 | let a = [1, 2, 3, 4, 5]; 10 | 11 | // TODO: Get a slice called `nice_slice` out of the array `a` so that the test passes. 12 | // let nice_slice = ??? 13 | 14 | assert_eq!([2, 3, 4], nice_slice); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /exercises/04_primitive_types/primitive_types5.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | let cat = ("Furry McFurson", 3.5); 3 | 4 | // TODO: Destructure the `cat` tuple in one statement so that the println works. 5 | // let /* your pattern here */ = cat; 6 | 7 | println!("{name} is {age} years old"); 8 | } 9 | -------------------------------------------------------------------------------- /exercises/04_primitive_types/primitive_types6.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | // You can optionally experiment here. 3 | } 4 | 5 | #[cfg(test)] 6 | mod tests { 7 | #[test] 8 | fn indexing_tuple() { 9 | let numbers = (1, 2, 3); 10 | 11 | // TODO: Use a tuple index to access the second element of `numbers` 12 | // and assign it to a variable called `second`. 13 | // let second = ???; 14 | 15 | assert_eq!(second, 2, "This is not the 2nd number in the tuple!"); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /exercises/05_vecs/README.md: -------------------------------------------------------------------------------- 1 | # Vectors 2 | 3 | Vectors are one of the most-used Rust data structures. In other programming 4 | languages, they'd simply be called Arrays, but since Rust operates on a 5 | bit of a lower level, an array in Rust is stored on the stack (meaning it 6 | can't grow or shrink, and the size needs to be known at compile time), 7 | and a Vector is stored in the heap (where these restrictions do not apply). 8 | 9 | Vectors are a bit of a later chapter in the book, but we think that they're 10 | useful enough to talk about them a bit earlier. We shall be talking about 11 | the other useful data structure, hash maps, later. 12 | 13 | ## Further information 14 | 15 | - [Storing Lists of Values with Vectors](https://doc.rust-lang.org/book/ch08-01-vectors.html) 16 | - [`iter_mut`](https://doc.rust-lang.org/std/primitive.slice.html#method.iter_mut) 17 | - [`map`](https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.map) 18 | -------------------------------------------------------------------------------- /exercises/05_vecs/vecs1.rs: -------------------------------------------------------------------------------- 1 | fn array_and_vec() -> ([i32; 4], Vec) { 2 | let a = [10, 20, 30, 40]; // Array 3 | 4 | // TODO: Create a vector called `v` which contains the exact same elements as in the array `a`. 5 | // Use the vector macro. 6 | // let v = ???; 7 | 8 | (a, v) 9 | } 10 | 11 | fn main() { 12 | // You can optionally experiment here. 13 | } 14 | 15 | #[cfg(test)] 16 | mod tests { 17 | use super::*; 18 | 19 | #[test] 20 | fn test_array_and_vec_similarity() { 21 | let (a, v) = array_and_vec(); 22 | assert_eq!(a, *v); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /exercises/05_vecs/vecs2.rs: -------------------------------------------------------------------------------- 1 | fn vec_loop(input: &[i32]) -> Vec { 2 | let mut output = Vec::new(); 3 | 4 | for element in input { 5 | // TODO: Multiply each element in the `input` slice by 2 and push it to 6 | // the `output` vector. 7 | } 8 | 9 | output 10 | } 11 | 12 | fn vec_map_example(input: &[i32]) -> Vec { 13 | // An example of collecting a vector after mapping. 14 | // We map each element of the `input` slice to its value plus 1. 15 | // If the input is `[1, 2, 3]`, the output is `[2, 3, 4]`. 16 | input.iter().map(|element| element + 1).collect() 17 | } 18 | 19 | fn vec_map(input: &[i32]) -> Vec { 20 | // TODO: Here, we also want to multiply each element in the `input` slice 21 | // by 2, but with iterator mapping instead of manually pushing into an empty 22 | // vector. 23 | // See the example in the function `vec_map_example` above. 24 | input 25 | .iter() 26 | .map(|element| { 27 | // ??? 28 | }) 29 | .collect() 30 | } 31 | 32 | fn main() { 33 | // You can optionally experiment here. 34 | } 35 | 36 | #[cfg(test)] 37 | mod tests { 38 | use super::*; 39 | 40 | #[test] 41 | fn test_vec_loop() { 42 | let input = [2, 4, 6, 8, 10]; 43 | let ans = vec_loop(&input); 44 | assert_eq!(ans, [4, 8, 12, 16, 20]); 45 | } 46 | 47 | #[test] 48 | fn test_vec_map_example() { 49 | let input = [1, 2, 3]; 50 | let ans = vec_map_example(&input); 51 | assert_eq!(ans, [2, 3, 4]); 52 | } 53 | 54 | #[test] 55 | fn test_vec_map() { 56 | let input = [2, 4, 6, 8, 10]; 57 | let ans = vec_map(&input); 58 | assert_eq!(ans, [4, 8, 12, 16, 20]); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /exercises/06_move_semantics/README.md: -------------------------------------------------------------------------------- 1 | # Move Semantics 2 | 3 | These exercises are adapted from [pnkfelix](https://github.com/pnkfelix)'s [Rust Tutorial](https://pnkfelix.github.io/rust-examples-icfp2014/) -- Thank you Felix!!! 4 | 5 | ## Further information 6 | 7 | For this section, the book links are especially important. 8 | 9 | - [Ownership](https://doc.rust-lang.org/book/ch04-01-what-is-ownership.html) 10 | - [Reference and borrowing](https://doc.rust-lang.org/book/ch04-02-references-and-borrowing.html) 11 | -------------------------------------------------------------------------------- /exercises/06_move_semantics/move_semantics1.rs: -------------------------------------------------------------------------------- 1 | // TODO: Fix the compiler error in this function. 2 | fn fill_vec(vec: Vec) -> Vec { 3 | let vec = vec; 4 | 5 | vec.push(88); 6 | 7 | vec 8 | } 9 | 10 | fn main() { 11 | // You can optionally experiment here. 12 | } 13 | 14 | #[cfg(test)] 15 | mod tests { 16 | use super::*; 17 | 18 | #[test] 19 | fn move_semantics1() { 20 | let vec0 = vec![22, 44, 66]; 21 | let vec1 = fill_vec(vec0); 22 | assert_eq!(vec1, vec![22, 44, 66, 88]); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /exercises/06_move_semantics/move_semantics2.rs: -------------------------------------------------------------------------------- 1 | fn fill_vec(vec: Vec) -> Vec { 2 | let mut vec = vec; 3 | 4 | vec.push(88); 5 | 6 | vec 7 | } 8 | 9 | fn main() { 10 | // You can optionally experiment here. 11 | } 12 | 13 | #[cfg(test)] 14 | mod tests { 15 | use super::*; 16 | 17 | // TODO: Make both vectors `vec0` and `vec1` accessible at the same time to 18 | // fix the compiler error in the test. 19 | #[test] 20 | fn move_semantics2() { 21 | let vec0 = vec![22, 44, 66]; 22 | 23 | let vec1 = fill_vec(vec0); 24 | 25 | assert_eq!(vec0, [22, 44, 66]); 26 | assert_eq!(vec1, [22, 44, 66, 88]); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /exercises/06_move_semantics/move_semantics3.rs: -------------------------------------------------------------------------------- 1 | // TODO: Fix the compiler error in the function without adding any new line. 2 | fn fill_vec(vec: Vec) -> Vec { 3 | vec.push(88); 4 | 5 | vec 6 | } 7 | 8 | fn main() { 9 | // You can optionally experiment here. 10 | } 11 | 12 | #[cfg(test)] 13 | mod tests { 14 | use super::*; 15 | 16 | #[test] 17 | fn move_semantics3() { 18 | let vec0 = vec![22, 44, 66]; 19 | let vec1 = fill_vec(vec0); 20 | assert_eq!(vec1, [22, 44, 66, 88]); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /exercises/06_move_semantics/move_semantics4.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | // You can optionally experiment here. 3 | } 4 | 5 | #[cfg(test)] 6 | mod tests { 7 | // TODO: Fix the compiler errors only by reordering the lines in the test. 8 | // Don't add, change or remove any line. 9 | #[test] 10 | fn move_semantics4() { 11 | let mut x = Vec::new(); 12 | let y = &mut x; 13 | let z = &mut x; 14 | y.push(42); 15 | z.push(13); 16 | assert_eq!(x, [42, 13]); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /exercises/06_move_semantics/move_semantics5.rs: -------------------------------------------------------------------------------- 1 | #![allow(clippy::ptr_arg)] 2 | 3 | // TODO: Fix the compiler errors without changing anything except adding or 4 | // removing references (the character `&`). 5 | 6 | // Shouldn't take ownership 7 | fn get_char(data: String) -> char { 8 | data.chars().last().unwrap() 9 | } 10 | 11 | // Should take ownership 12 | fn string_uppercase(mut data: &String) { 13 | data = data.to_uppercase(); 14 | 15 | println!("{data}"); 16 | } 17 | 18 | fn main() { 19 | let data = "Rust is great!".to_string(); 20 | 21 | get_char(data); 22 | 23 | string_uppercase(&data); 24 | } 25 | -------------------------------------------------------------------------------- /exercises/07_structs/README.md: -------------------------------------------------------------------------------- 1 | # Structs 2 | 3 | Rust has three struct types: a classic C struct, a tuple struct, and a unit struct. 4 | 5 | ## Further information 6 | 7 | - [Structures](https://doc.rust-lang.org/book/ch05-01-defining-structs.html) 8 | - [Method Syntax](https://doc.rust-lang.org/book/ch05-03-method-syntax.html) 9 | -------------------------------------------------------------------------------- /exercises/07_structs/structs1.rs: -------------------------------------------------------------------------------- 1 | struct ColorRegularStruct { 2 | // TODO: Add the fields that the test `regular_structs` expects. 3 | // What types should the fields have? What are the minimum and maximum values for RGB colors? 4 | } 5 | 6 | struct ColorTupleStruct(/* TODO: Add the fields that the test `tuple_structs` expects */); 7 | 8 | #[derive(Debug)] 9 | struct UnitStruct; 10 | 11 | fn main() { 12 | // You can optionally experiment here. 13 | } 14 | 15 | #[cfg(test)] 16 | mod tests { 17 | use super::*; 18 | 19 | #[test] 20 | fn regular_structs() { 21 | // TODO: Instantiate a regular struct. 22 | // let green = 23 | 24 | assert_eq!(green.red, 0); 25 | assert_eq!(green.green, 255); 26 | assert_eq!(green.blue, 0); 27 | } 28 | 29 | #[test] 30 | fn tuple_structs() { 31 | // TODO: Instantiate a tuple struct. 32 | // let green = 33 | 34 | assert_eq!(green.0, 0); 35 | assert_eq!(green.1, 255); 36 | assert_eq!(green.2, 0); 37 | } 38 | 39 | #[test] 40 | fn unit_structs() { 41 | // TODO: Instantiate a unit struct. 42 | // let unit_struct = 43 | let message = format!("{unit_struct:?}s are fun!"); 44 | 45 | assert_eq!(message, "UnitStructs are fun!"); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /exercises/07_structs/structs2.rs: -------------------------------------------------------------------------------- 1 | #[derive(Debug)] 2 | struct Order { 3 | name: String, 4 | year: u32, 5 | made_by_phone: bool, 6 | made_by_mobile: bool, 7 | made_by_email: bool, 8 | item_number: u32, 9 | count: u32, 10 | } 11 | 12 | fn create_order_template() -> Order { 13 | Order { 14 | name: String::from("Bob"), 15 | year: 2019, 16 | made_by_phone: false, 17 | made_by_mobile: false, 18 | made_by_email: true, 19 | item_number: 123, 20 | count: 0, 21 | } 22 | } 23 | 24 | fn main() { 25 | // You can optionally experiment here. 26 | } 27 | 28 | #[cfg(test)] 29 | mod tests { 30 | use super::*; 31 | 32 | #[test] 33 | fn your_order() { 34 | let order_template = create_order_template(); 35 | 36 | // TODO: Create your own order using the update syntax and template above! 37 | // let your_order = 38 | 39 | assert_eq!(your_order.name, "Hacker in Rust"); 40 | assert_eq!(your_order.year, order_template.year); 41 | assert_eq!(your_order.made_by_phone, order_template.made_by_phone); 42 | assert_eq!(your_order.made_by_mobile, order_template.made_by_mobile); 43 | assert_eq!(your_order.made_by_email, order_template.made_by_email); 44 | assert_eq!(your_order.item_number, order_template.item_number); 45 | assert_eq!(your_order.count, 1); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /exercises/08_enums/README.md: -------------------------------------------------------------------------------- 1 | # Enums 2 | 3 | Rust allows you to define types called "enums" which enumerate possible values. 4 | Enums are a feature in many languages, but their capabilities differ in each language. Rust's enums are most similar to algebraic data types in functional languages, such as F#, OCaml, and Haskell. 5 | Useful in combination with enums is Rust's "pattern matching" facility, which makes it easy to run different code for different values of an enumeration. 6 | 7 | ## Further information 8 | 9 | - [Enums](https://doc.rust-lang.org/book/ch06-00-enums.html) 10 | - [Pattern syntax](https://doc.rust-lang.org/book/ch19-03-pattern-syntax.html) 11 | -------------------------------------------------------------------------------- /exercises/08_enums/enums1.rs: -------------------------------------------------------------------------------- 1 | #[derive(Debug)] 2 | enum Message { 3 | // TODO: Define a few types of messages as used below. 4 | } 5 | 6 | fn main() { 7 | println!("{:?}", Message::Resize); 8 | println!("{:?}", Message::Move); 9 | println!("{:?}", Message::Echo); 10 | println!("{:?}", Message::ChangeColor); 11 | println!("{:?}", Message::Quit); 12 | } 13 | -------------------------------------------------------------------------------- /exercises/08_enums/enums2.rs: -------------------------------------------------------------------------------- 1 | #[derive(Debug)] 2 | struct Point { 3 | x: u64, 4 | y: u64, 5 | } 6 | 7 | #[derive(Debug)] 8 | enum Message { 9 | // TODO: Define the different variants used below. 10 | } 11 | 12 | impl Message { 13 | fn call(&self) { 14 | println!("{self:?}"); 15 | } 16 | } 17 | 18 | fn main() { 19 | let messages = [ 20 | Message::Resize { 21 | width: 10, 22 | height: 30, 23 | }, 24 | Message::Move(Point { x: 10, y: 15 }), 25 | Message::Echo(String::from("hello world")), 26 | Message::ChangeColor(200, 255, 255), 27 | Message::Quit, 28 | ]; 29 | 30 | for message in &messages { 31 | message.call(); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /exercises/08_enums/enums3.rs: -------------------------------------------------------------------------------- 1 | struct Point { 2 | x: u64, 3 | y: u64, 4 | } 5 | 6 | enum Message { 7 | Resize { width: u64, height: u64 }, 8 | Move(Point), 9 | Echo(String), 10 | ChangeColor(u8, u8, u8), 11 | Quit, 12 | } 13 | 14 | struct State { 15 | width: u64, 16 | height: u64, 17 | position: Point, 18 | message: String, 19 | // RGB color composed of red, green and blue. 20 | color: (u8, u8, u8), 21 | quit: bool, 22 | } 23 | 24 | impl State { 25 | fn resize(&mut self, width: u64, height: u64) { 26 | self.width = width; 27 | self.height = height; 28 | } 29 | 30 | fn move_position(&mut self, point: Point) { 31 | self.position = point; 32 | } 33 | 34 | fn echo(&mut self, s: String) { 35 | self.message = s; 36 | } 37 | 38 | fn change_color(&mut self, red: u8, green: u8, blue: u8) { 39 | self.color = (red, green, blue); 40 | } 41 | 42 | fn quit(&mut self) { 43 | self.quit = true; 44 | } 45 | 46 | fn process(&mut self, message: Message) { 47 | // TODO: Create a match expression to process the different message 48 | // variants using the methods defined above. 49 | } 50 | } 51 | 52 | fn main() { 53 | // You can optionally experiment here. 54 | } 55 | 56 | #[cfg(test)] 57 | mod tests { 58 | use super::*; 59 | 60 | #[test] 61 | fn test_match_message_call() { 62 | let mut state = State { 63 | width: 0, 64 | height: 0, 65 | position: Point { x: 0, y: 0 }, 66 | message: String::from("hello world"), 67 | color: (0, 0, 0), 68 | quit: false, 69 | }; 70 | 71 | state.process(Message::Resize { 72 | width: 10, 73 | height: 30, 74 | }); 75 | state.process(Message::Move(Point { x: 10, y: 15 })); 76 | state.process(Message::Echo(String::from("Hello world!"))); 77 | state.process(Message::ChangeColor(255, 0, 255)); 78 | state.process(Message::Quit); 79 | 80 | assert_eq!(state.width, 10); 81 | assert_eq!(state.height, 30); 82 | assert_eq!(state.position.x, 10); 83 | assert_eq!(state.position.y, 15); 84 | assert_eq!(state.message, "Hello world!"); 85 | assert_eq!(state.color, (255, 0, 255)); 86 | assert!(state.quit); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /exercises/09_strings/README.md: -------------------------------------------------------------------------------- 1 | # Strings 2 | 3 | Rust has two string types, a string slice (`&str`) and an owned string (`String`). 4 | We're not going to dictate when you should use which one, but we'll show you how 5 | to identify and create them, as well as use them. 6 | 7 | ## Further information 8 | 9 | - [Strings](https://doc.rust-lang.org/book/ch08-02-strings.html) 10 | -------------------------------------------------------------------------------- /exercises/09_strings/strings1.rs: -------------------------------------------------------------------------------- 1 | // TODO: Fix the compiler error without changing the function signature. 2 | fn current_favorite_color() -> String { 3 | "blue" 4 | } 5 | 6 | fn main() { 7 | let answer = current_favorite_color(); 8 | println!("My current favorite color is {answer}"); 9 | } 10 | -------------------------------------------------------------------------------- /exercises/09_strings/strings2.rs: -------------------------------------------------------------------------------- 1 | // TODO: Fix the compiler error in the `main` function without changing this function. 2 | fn is_a_color_word(attempt: &str) -> bool { 3 | attempt == "green" || attempt == "blue" || attempt == "red" 4 | } 5 | 6 | fn main() { 7 | let word = String::from("green"); // Don't change this line. 8 | 9 | if is_a_color_word(word) { 10 | println!("That is a color word I know!"); 11 | } else { 12 | println!("That is not a color word I know."); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /exercises/09_strings/strings3.rs: -------------------------------------------------------------------------------- 1 | fn trim_me(input: &str) -> &str { 2 | // TODO: Remove whitespace from both ends of a string. 3 | } 4 | 5 | fn compose_me(input: &str) -> String { 6 | // TODO: Add " world!" to the string! There are multiple ways to do this. 7 | } 8 | 9 | fn replace_me(input: &str) -> String { 10 | // TODO: Replace "cars" in the string with "balloons". 11 | } 12 | 13 | fn main() { 14 | // You can optionally experiment here. 15 | } 16 | 17 | #[cfg(test)] 18 | mod tests { 19 | use super::*; 20 | 21 | #[test] 22 | fn trim_a_string() { 23 | assert_eq!(trim_me("Hello! "), "Hello!"); 24 | assert_eq!(trim_me(" What's up!"), "What's up!"); 25 | assert_eq!(trim_me(" Hola! "), "Hola!"); 26 | assert_eq!(trim_me("Hi!"), "Hi!"); 27 | } 28 | 29 | #[test] 30 | fn compose_a_string() { 31 | assert_eq!(compose_me("Hello"), "Hello world!"); 32 | assert_eq!(compose_me("Goodbye"), "Goodbye world!"); 33 | } 34 | 35 | #[test] 36 | fn replace_a_string() { 37 | assert_eq!( 38 | replace_me("I think cars are cool"), 39 | "I think balloons are cool", 40 | ); 41 | assert_eq!( 42 | replace_me("I love to look at cars"), 43 | "I love to look at balloons", 44 | ); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /exercises/09_strings/strings4.rs: -------------------------------------------------------------------------------- 1 | // Calls of this function should be replaced with calls of `string_slice` or `string`. 2 | fn placeholder() {} 3 | 4 | fn string_slice(arg: &str) { 5 | println!("{arg}"); 6 | } 7 | 8 | fn string(arg: String) { 9 | println!("{arg}"); 10 | } 11 | 12 | // TODO: Here are a bunch of values - some are `String`, some are `&str`. 13 | // Your task is to replace `placeholder(…)` with either `string_slice(…)` 14 | // or `string(…)` depending on what you think each value is. 15 | fn main() { 16 | placeholder("blue"); 17 | 18 | placeholder("red".to_string()); 19 | 20 | placeholder(String::from("hi")); 21 | 22 | placeholder("rust is fun!".to_owned()); 23 | 24 | placeholder("nice weather".into()); 25 | 26 | placeholder(format!("Interpolation {}", "Station")); 27 | 28 | // WARNING: This is byte indexing, not character indexing. 29 | // Character indexing can be done using `s.chars().nth(INDEX)`. 30 | placeholder(&String::from("abc")[0..1]); 31 | 32 | placeholder(" hello there ".trim()); 33 | 34 | placeholder("Happy Monday!".replace("Mon", "Tues")); 35 | 36 | placeholder("mY sHiFt KeY iS sTiCkY".to_lowercase()); 37 | } 38 | -------------------------------------------------------------------------------- /exercises/10_modules/README.md: -------------------------------------------------------------------------------- 1 | # Modules 2 | 3 | In this section we'll give you an introduction to Rust's module system. 4 | 5 | ## Further information 6 | 7 | - [The Module System](https://doc.rust-lang.org/book/ch07-00-managing-growing-projects-with-packages-crates-and-modules.html) 8 | -------------------------------------------------------------------------------- /exercises/10_modules/modules1.rs: -------------------------------------------------------------------------------- 1 | // TODO: Fix the compiler error about calling a private function. 2 | mod sausage_factory { 3 | // Don't let anybody outside of this module see this! 4 | fn get_secret_recipe() -> String { 5 | String::from("Ginger") 6 | } 7 | 8 | fn make_sausage() { 9 | get_secret_recipe(); 10 | println!("sausage!"); 11 | } 12 | } 13 | 14 | fn main() { 15 | sausage_factory::make_sausage(); 16 | } 17 | -------------------------------------------------------------------------------- /exercises/10_modules/modules2.rs: -------------------------------------------------------------------------------- 1 | // You can bring module paths into scopes and provide new names for them with 2 | // the `use` and `as` keywords. 3 | 4 | mod delicious_snacks { 5 | // TODO: Add the following two `use` statements after fixing them. 6 | // use self::fruits::PEAR as ???; 7 | // use self::veggies::CUCUMBER as ???; 8 | 9 | mod fruits { 10 | pub const PEAR: &str = "Pear"; 11 | pub const APPLE: &str = "Apple"; 12 | } 13 | 14 | mod veggies { 15 | pub const CUCUMBER: &str = "Cucumber"; 16 | pub const CARROT: &str = "Carrot"; 17 | } 18 | } 19 | 20 | fn main() { 21 | println!( 22 | "favorite snacks: {} and {}", 23 | delicious_snacks::fruit, 24 | delicious_snacks::veggie, 25 | ); 26 | } 27 | -------------------------------------------------------------------------------- /exercises/10_modules/modules3.rs: -------------------------------------------------------------------------------- 1 | // You can use the `use` keyword to bring module paths from modules from 2 | // anywhere and especially from the standard library into your scope. 3 | 4 | // TODO: Bring `SystemTime` and `UNIX_EPOCH` from the `std::time` module into 5 | // your scope. Bonus style points if you can do it with one line! 6 | // use ???; 7 | 8 | fn main() { 9 | match SystemTime::now().duration_since(UNIX_EPOCH) { 10 | Ok(n) => println!("1970-01-01 00:00:00 UTC was {} seconds ago!", n.as_secs()), 11 | Err(_) => panic!("SystemTime before UNIX EPOCH!"), 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /exercises/11_hashmaps/README.md: -------------------------------------------------------------------------------- 1 | # Hashmaps 2 | 3 | A *hash map* allows you to associate a value with a particular key. 4 | You may also know this by the names [*unordered map* in C++](https://en.cppreference.com/w/cpp/container/unordered_map), 5 | [*dictionary* in Python](https://docs.python.org/3/tutorial/datastructures.html#dictionaries) or an *associative array* in other languages. 6 | 7 | This is the other data structure that we've been talking about before, when 8 | talking about Vecs. 9 | 10 | ## Further information 11 | 12 | - [Storing Keys with Associated Values in Hash Maps](https://doc.rust-lang.org/book/ch08-03-hash-maps.html) 13 | -------------------------------------------------------------------------------- /exercises/11_hashmaps/hashmaps1.rs: -------------------------------------------------------------------------------- 1 | // A basket of fruits in the form of a hash map needs to be defined. The key 2 | // represents the name of the fruit and the value represents how many of that 3 | // particular fruit is in the basket. You have to put at least 3 different 4 | // types of fruits (e.g. apple, banana, mango) in the basket and the total count 5 | // of all the fruits should be at least 5. 6 | 7 | use std::collections::HashMap; 8 | 9 | fn fruit_basket() -> HashMap { 10 | // TODO: Declare the hash map. 11 | // let mut basket = 12 | 13 | // Two bananas are already given for you :) 14 | basket.insert(String::from("banana"), 2); 15 | 16 | // TODO: Put more fruits in your basket. 17 | 18 | basket 19 | } 20 | 21 | fn main() { 22 | // You can optionally experiment here. 23 | } 24 | 25 | #[cfg(test)] 26 | mod tests { 27 | use super::*; 28 | 29 | #[test] 30 | fn at_least_three_types_of_fruits() { 31 | let basket = fruit_basket(); 32 | assert!(basket.len() >= 3); 33 | } 34 | 35 | #[test] 36 | fn at_least_five_fruits() { 37 | let basket = fruit_basket(); 38 | assert!(basket.values().sum::() >= 5); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /exercises/12_options/README.md: -------------------------------------------------------------------------------- 1 | # Options 2 | 3 | Type Option represents an optional value: every Option is either Some and contains a value, or None, and does not. 4 | Option types are very common in Rust code, as they have a number of uses: 5 | 6 | - Initial values 7 | - Return values for functions that are not defined over their entire input range (partial functions) 8 | - Return value for otherwise reporting simple errors, where None is returned on error 9 | - Optional struct fields 10 | - Struct fields that can be loaned or "taken" 11 | - Optional function arguments 12 | - Nullable pointers 13 | - Swapping things out of difficult situations 14 | 15 | ## Further Information 16 | 17 | - [Option Enum Format](https://doc.rust-lang.org/book/ch10-01-syntax.html#in-enum-definitions) 18 | - [Option Module Documentation](https://doc.rust-lang.org/std/option/) 19 | - [Option Enum Documentation](https://doc.rust-lang.org/std/option/enum.Option.html) 20 | - [if let](https://doc.rust-lang.org/rust-by-example/flow_control/if_let.html) 21 | - [while let](https://doc.rust-lang.org/rust-by-example/flow_control/while_let.html) 22 | -------------------------------------------------------------------------------- /exercises/12_options/options1.rs: -------------------------------------------------------------------------------- 1 | // This function returns how much ice cream there is left in the fridge. 2 | // If it's before 22:00 (24-hour system), then 5 scoops are left. At 22:00, 3 | // someone eats it all, so no ice cream is left (value 0). Return `None` if 4 | // `hour_of_day` is higher than 23. 5 | fn maybe_ice_cream(hour_of_day: u16) -> Option { 6 | // TODO: Complete the function body. 7 | } 8 | 9 | fn main() { 10 | // You can optionally experiment here. 11 | } 12 | 13 | #[cfg(test)] 14 | mod tests { 15 | use super::*; 16 | 17 | #[test] 18 | fn raw_value() { 19 | // TODO: Fix this test. How do you get the value contained in the 20 | // Option? 21 | let ice_creams = maybe_ice_cream(12); 22 | 23 | assert_eq!(ice_creams, 5); // Don't change this line. 24 | } 25 | 26 | #[test] 27 | fn check_ice_cream() { 28 | assert_eq!(maybe_ice_cream(0), Some(5)); 29 | assert_eq!(maybe_ice_cream(9), Some(5)); 30 | assert_eq!(maybe_ice_cream(18), Some(5)); 31 | assert_eq!(maybe_ice_cream(22), Some(0)); 32 | assert_eq!(maybe_ice_cream(23), Some(0)); 33 | assert_eq!(maybe_ice_cream(24), None); 34 | assert_eq!(maybe_ice_cream(25), None); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /exercises/12_options/options2.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | // You can optionally experiment here. 3 | } 4 | 5 | #[cfg(test)] 6 | mod tests { 7 | #[test] 8 | fn simple_option() { 9 | let target = "rustlings"; 10 | let optional_target = Some(target); 11 | 12 | // TODO: Make this an if-let statement whose value is `Some`. 13 | word = optional_target { 14 | assert_eq!(word, target); 15 | } 16 | } 17 | 18 | #[test] 19 | fn layered_option() { 20 | let range = 10; 21 | let mut optional_integers: Vec> = vec![None]; 22 | 23 | for i in 1..=range { 24 | optional_integers.push(Some(i)); 25 | } 26 | 27 | let mut cursor = range; 28 | 29 | // TODO: Make this a while-let statement. Remember that `Vec::pop()` 30 | // adds another layer of `Option`. You can do nested pattern matching 31 | // in if-let and while-let statements. 32 | integer = optional_integers.pop() { 33 | assert_eq!(integer, cursor); 34 | cursor -= 1; 35 | } 36 | 37 | assert_eq!(cursor, 0); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /exercises/12_options/options3.rs: -------------------------------------------------------------------------------- 1 | #[derive(Debug)] 2 | struct Point { 3 | x: i32, 4 | y: i32, 5 | } 6 | 7 | fn main() { 8 | let optional_point = Some(Point { x: 100, y: 200 }); 9 | 10 | // TODO: Fix the compiler error by adding something to this match statement. 11 | match optional_point { 12 | Some(p) => println!("Coordinates are {},{}", p.x, p.y), 13 | _ => panic!("No match!"), 14 | } 15 | 16 | println!("{optional_point:?}"); // Don't change this line. 17 | } 18 | -------------------------------------------------------------------------------- /exercises/13_error_handling/README.md: -------------------------------------------------------------------------------- 1 | # Error handling 2 | 3 | Most errors aren't serious enough to require the program to stop entirely. 4 | Sometimes, when a function fails, it's for a reason that you can easily interpret and respond to. 5 | For example, if you try to open a file and that operation fails because the file doesn't exist, you might want to create the file instead of terminating the process. 6 | 7 | ## Further information 8 | 9 | - [Error Handling](https://doc.rust-lang.org/book/ch09-02-recoverable-errors-with-result.html) 10 | - [Generics](https://doc.rust-lang.org/book/ch10-01-syntax.html) 11 | - [Result](https://doc.rust-lang.org/rust-by-example/error/result.html) 12 | - [Boxing errors](https://doc.rust-lang.org/rust-by-example/error/multiple_error_types/boxing_errors.html) 13 | -------------------------------------------------------------------------------- /exercises/13_error_handling/errors1.rs: -------------------------------------------------------------------------------- 1 | // TODO: This function refuses to generate text to be printed on a nametag if 2 | // you pass it an empty string. It'd be nicer if it explained what the problem 3 | // was instead of just returning `None`. Thankfully, Rust has a similar 4 | // construct to `Option` that can be used to express error conditions. Change 5 | // the function signature and body to return `Result` instead 6 | // of `Option`. 7 | fn generate_nametag_text(name: String) -> Option { 8 | if name.is_empty() { 9 | // Empty names aren't allowed 10 | None 11 | } else { 12 | Some(format!("Hi! My name is {name}")) 13 | } 14 | } 15 | 16 | fn main() { 17 | // You can optionally experiment here. 18 | } 19 | 20 | #[cfg(test)] 21 | mod tests { 22 | use super::*; 23 | 24 | #[test] 25 | fn generates_nametag_text_for_a_nonempty_name() { 26 | assert_eq!( 27 | generate_nametag_text("Beyoncé".to_string()).as_deref(), 28 | Ok("Hi! My name is Beyoncé"), 29 | ); 30 | } 31 | 32 | #[test] 33 | fn explains_why_generating_nametag_text_fails() { 34 | assert_eq!( 35 | generate_nametag_text(String::new()) 36 | .as_ref() 37 | .map_err(|e| e.as_str()), 38 | Err("Empty names aren't allowed"), 39 | ); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /exercises/13_error_handling/errors2.rs: -------------------------------------------------------------------------------- 1 | // Say we're writing a game where you can buy items with tokens. All items cost 2 | // 5 tokens, and whenever you purchase items there is a processing fee of 1 3 | // token. A player of the game will type in how many items they want to buy, and 4 | // the `total_cost` function will calculate the total cost of the items. Since 5 | // the player typed in the quantity, we get it as a string. They might have 6 | // typed anything, not just numbers! 7 | // 8 | // Right now, this function isn't handling the error case at all. What we want 9 | // to do is: If we call the `total_cost` function on a string that is not a 10 | // number, that function will return a `ParseIntError`. In that case, we want to 11 | // immediately return that error from our function and not try to multiply and 12 | // add. 13 | // 14 | // There are at least two ways to implement this that are both correct. But one 15 | // is a lot shorter! 16 | 17 | use std::num::ParseIntError; 18 | 19 | fn total_cost(item_quantity: &str) -> Result { 20 | let processing_fee = 1; 21 | let cost_per_item = 5; 22 | 23 | // TODO: Handle the error case as described above. 24 | let qty = item_quantity.parse::(); 25 | 26 | Ok(qty * cost_per_item + processing_fee) 27 | } 28 | 29 | fn main() { 30 | // You can optionally experiment here. 31 | } 32 | 33 | #[cfg(test)] 34 | mod tests { 35 | use super::*; 36 | use std::num::IntErrorKind; 37 | 38 | #[test] 39 | fn item_quantity_is_a_valid_number() { 40 | assert_eq!(total_cost("34"), Ok(171)); 41 | } 42 | 43 | #[test] 44 | fn item_quantity_is_an_invalid_number() { 45 | assert_eq!( 46 | total_cost("beep boop").unwrap_err().kind(), 47 | &IntErrorKind::InvalidDigit, 48 | ); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /exercises/13_error_handling/errors3.rs: -------------------------------------------------------------------------------- 1 | // This is a program that is trying to use a completed version of the 2 | // `total_cost` function from the previous exercise. It's not working though! 3 | // Why not? What should we do to fix it? 4 | 5 | use std::num::ParseIntError; 6 | 7 | // Don't change this function. 8 | fn total_cost(item_quantity: &str) -> Result { 9 | let processing_fee = 1; 10 | let cost_per_item = 5; 11 | let qty = item_quantity.parse::()?; 12 | 13 | Ok(qty * cost_per_item + processing_fee) 14 | } 15 | 16 | // TODO: Fix the compiler error by changing the signature and body of the 17 | // `main` function. 18 | fn main() { 19 | let mut tokens = 100; 20 | let pretend_user_input = "8"; 21 | 22 | // Don't change this line. 23 | let cost = total_cost(pretend_user_input)?; 24 | 25 | if cost > tokens { 26 | println!("You can't afford that many!"); 27 | } else { 28 | tokens -= cost; 29 | println!("You now have {tokens} tokens."); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /exercises/13_error_handling/errors4.rs: -------------------------------------------------------------------------------- 1 | #[derive(PartialEq, Debug)] 2 | enum CreationError { 3 | Negative, 4 | Zero, 5 | } 6 | 7 | #[derive(PartialEq, Debug)] 8 | struct PositiveNonzeroInteger(u64); 9 | 10 | impl PositiveNonzeroInteger { 11 | fn new(value: i64) -> Result { 12 | // TODO: This function shouldn't always return an `Ok`. 13 | Ok(Self(value as u64)) 14 | } 15 | } 16 | 17 | fn main() { 18 | // You can optionally experiment here. 19 | } 20 | 21 | #[cfg(test)] 22 | mod tests { 23 | use super::*; 24 | 25 | #[test] 26 | fn test_creation() { 27 | assert_eq!( 28 | PositiveNonzeroInteger::new(10), 29 | Ok(PositiveNonzeroInteger(10)), 30 | ); 31 | assert_eq!( 32 | PositiveNonzeroInteger::new(-10), 33 | Err(CreationError::Negative), 34 | ); 35 | assert_eq!(PositiveNonzeroInteger::new(0), Err(CreationError::Zero)); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /exercises/13_error_handling/errors5.rs: -------------------------------------------------------------------------------- 1 | // This exercise is an altered version of the `errors4` exercise. It uses some 2 | // concepts that we won't get to until later in the course, like `Box` and the 3 | // `From` trait. It's not important to understand them in detail right now, but 4 | // you can read ahead if you like. For now, think of the `Box` type as 5 | // an "I want anything that does ???" type. 6 | // 7 | // In short, this particular use case for boxes is for when you want to own a 8 | // value and you care only that it is a type which implements a particular 9 | // trait. To do so, the `Box` is declared as of type `Box` where 10 | // `Trait` is the trait the compiler looks for on any value used in that 11 | // context. For this exercise, that context is the potential errors which 12 | // can be returned in a `Result`. 13 | 14 | use std::error::Error; 15 | use std::fmt; 16 | 17 | #[derive(PartialEq, Debug)] 18 | enum CreationError { 19 | Negative, 20 | Zero, 21 | } 22 | 23 | // This is required so that `CreationError` can implement `Error`. 24 | impl fmt::Display for CreationError { 25 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 26 | let description = match *self { 27 | CreationError::Negative => "number is negative", 28 | CreationError::Zero => "number is zero", 29 | }; 30 | f.write_str(description) 31 | } 32 | } 33 | 34 | impl Error for CreationError {} 35 | 36 | #[derive(PartialEq, Debug)] 37 | struct PositiveNonzeroInteger(u64); 38 | 39 | impl PositiveNonzeroInteger { 40 | fn new(value: i64) -> Result { 41 | match value { 42 | x if x < 0 => Err(CreationError::Negative), 43 | 0 => Err(CreationError::Zero), 44 | x => Ok(PositiveNonzeroInteger(x as u64)), 45 | } 46 | } 47 | } 48 | 49 | // TODO: Add the correct return type `Result<(), Box>`. What can we 50 | // use to describe both errors? Is there a trait which both errors implement? 51 | fn main() { 52 | let pretend_user_input = "42"; 53 | let x: i64 = pretend_user_input.parse()?; 54 | println!("output={:?}", PositiveNonzeroInteger::new(x)?); 55 | Ok(()) 56 | } 57 | -------------------------------------------------------------------------------- /exercises/14_generics/README.md: -------------------------------------------------------------------------------- 1 | # Generics 2 | 3 | Generics is the topic of generalizing types and functionalities to broader cases. 4 | This is extremely useful for reducing code duplication in many ways, but can call for some rather involved syntax. 5 | Namely, being generic requires taking great care to specify over which types a generic type is actually considered valid. 6 | The simplest and most common use of generics is for type parameters. 7 | 8 | ## Further information 9 | 10 | - [Generic Data Types](https://doc.rust-lang.org/book/ch10-01-syntax.html) 11 | - [Bounds](https://doc.rust-lang.org/rust-by-example/generics/bounds.html) 12 | -------------------------------------------------------------------------------- /exercises/14_generics/generics1.rs: -------------------------------------------------------------------------------- 1 | // `Vec` is generic over the type `T`. In most cases, the compiler is able to 2 | // infer `T`, for example after pushing a value with a concrete type to the vector. 3 | // But in this exercise, the compiler needs some help through a type annotation. 4 | 5 | fn main() { 6 | // TODO: Fix the compiler error by annotating the type of the vector 7 | // `Vec`. Choose `T` as some integer type that can be created from 8 | // `u8` and `i8`. 9 | let mut numbers = Vec::new(); 10 | 11 | // Don't change the lines below. 12 | let n1: u8 = 42; 13 | numbers.push(n1.into()); 14 | let n2: i8 = -1; 15 | numbers.push(n2.into()); 16 | 17 | println!("{numbers:?}"); 18 | } 19 | -------------------------------------------------------------------------------- /exercises/14_generics/generics2.rs: -------------------------------------------------------------------------------- 1 | // This powerful wrapper provides the ability to store a positive integer value. 2 | // TODO: Rewrite it using a generic so that it supports wrapping ANY type. 3 | struct Wrapper { 4 | value: u32, 5 | } 6 | 7 | // TODO: Adapt the struct's implementation to be generic over the wrapped value. 8 | impl Wrapper { 9 | fn new(value: u32) -> Self { 10 | Wrapper { value } 11 | } 12 | } 13 | 14 | fn main() { 15 | // You can optionally experiment here. 16 | } 17 | 18 | #[cfg(test)] 19 | mod tests { 20 | use super::*; 21 | 22 | #[test] 23 | fn store_u32_in_wrapper() { 24 | assert_eq!(Wrapper::new(42).value, 42); 25 | } 26 | 27 | #[test] 28 | fn store_str_in_wrapper() { 29 | assert_eq!(Wrapper::new("Foo").value, "Foo"); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /exercises/15_traits/README.md: -------------------------------------------------------------------------------- 1 | # Traits 2 | 3 | A trait is a collection of methods. 4 | 5 | Data types can implement traits. To do so, the methods making up the trait are defined for the data type. For example, the `String` data type implements the `From<&str>` trait. This allows a user to write `String::from("hello")`. 6 | 7 | In this way, traits are somewhat similar to Java interfaces and C++ abstract classes. 8 | 9 | Some additional common Rust traits include: 10 | 11 | - `Clone` (the `clone` method) 12 | - `Display` (which allows formatted display via `{}`) 13 | - `Debug` (which allows formatted display via `{:?}`) 14 | 15 | Because traits indicate shared behavior between data types, they are useful when writing generics. 16 | 17 | ## Further information 18 | 19 | - [Traits](https://doc.rust-lang.org/book/ch10-02-traits.html) 20 | -------------------------------------------------------------------------------- /exercises/15_traits/traits1.rs: -------------------------------------------------------------------------------- 1 | // The trait `AppendBar` has only one function which appends "Bar" to any object 2 | // implementing this trait. 3 | trait AppendBar { 4 | fn append_bar(self) -> Self; 5 | } 6 | 7 | impl AppendBar for String { 8 | // TODO: Implement `AppendBar` for the type `String`. 9 | } 10 | 11 | fn main() { 12 | let s = String::from("Foo"); 13 | let s = s.append_bar(); 14 | println!("s: {s}"); 15 | } 16 | 17 | #[cfg(test)] 18 | mod tests { 19 | use super::*; 20 | 21 | #[test] 22 | fn is_foo_bar() { 23 | assert_eq!(String::from("Foo").append_bar(), "FooBar"); 24 | } 25 | 26 | #[test] 27 | fn is_bar_bar() { 28 | assert_eq!(String::from("").append_bar().append_bar(), "BarBar"); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /exercises/15_traits/traits2.rs: -------------------------------------------------------------------------------- 1 | trait AppendBar { 2 | fn append_bar(self) -> Self; 3 | } 4 | 5 | // TODO: Implement the trait `AppendBar` for a vector of strings. 6 | // `append_bar` should push the string "Bar" into the vector. 7 | 8 | fn main() { 9 | // You can optionally experiment here. 10 | } 11 | 12 | #[cfg(test)] 13 | mod tests { 14 | use super::*; 15 | 16 | #[test] 17 | fn is_vec_pop_eq_bar() { 18 | let mut foo = vec![String::from("Foo")].append_bar(); 19 | assert_eq!(foo.pop().unwrap(), "Bar"); 20 | assert_eq!(foo.pop().unwrap(), "Foo"); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /exercises/15_traits/traits3.rs: -------------------------------------------------------------------------------- 1 | trait Licensed { 2 | // TODO: Add a default implementation for `licensing_info` so that 3 | // implementors like the two structs below can share that default behavior 4 | // without repeating the function. 5 | // The default license information should be the string "Default license". 6 | fn licensing_info(&self) -> String; 7 | } 8 | 9 | struct SomeSoftware { 10 | version_number: i32, 11 | } 12 | 13 | struct OtherSoftware { 14 | version_number: String, 15 | } 16 | 17 | impl Licensed for SomeSoftware {} // Don't edit this line. 18 | impl Licensed for OtherSoftware {} // Don't edit this line. 19 | 20 | fn main() { 21 | // You can optionally experiment here. 22 | } 23 | 24 | #[cfg(test)] 25 | mod tests { 26 | use super::*; 27 | 28 | #[test] 29 | fn is_licensing_info_the_same() { 30 | let licensing_info = "Default license"; 31 | let some_software = SomeSoftware { version_number: 1 }; 32 | let other_software = OtherSoftware { 33 | version_number: "v2.0.0".to_string(), 34 | }; 35 | assert_eq!(some_software.licensing_info(), licensing_info); 36 | assert_eq!(other_software.licensing_info(), licensing_info); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /exercises/15_traits/traits4.rs: -------------------------------------------------------------------------------- 1 | trait Licensed { 2 | fn licensing_info(&self) -> String { 3 | "Default license".to_string() 4 | } 5 | } 6 | 7 | struct SomeSoftware; 8 | struct OtherSoftware; 9 | 10 | impl Licensed for SomeSoftware {} 11 | impl Licensed for OtherSoftware {} 12 | 13 | // TODO: Fix the compiler error by only changing the signature of this function. 14 | fn compare_license_types(software1: ???, software2: ???) -> bool { 15 | software1.licensing_info() == software2.licensing_info() 16 | } 17 | 18 | fn main() { 19 | // You can optionally experiment here. 20 | } 21 | 22 | #[cfg(test)] 23 | mod tests { 24 | use super::*; 25 | 26 | #[test] 27 | fn compare_license_information() { 28 | assert!(compare_license_types(SomeSoftware, OtherSoftware)); 29 | } 30 | 31 | #[test] 32 | fn compare_license_information_backwards() { 33 | assert!(compare_license_types(OtherSoftware, SomeSoftware)); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /exercises/15_traits/traits5.rs: -------------------------------------------------------------------------------- 1 | trait SomeTrait { 2 | fn some_function(&self) -> bool { 3 | true 4 | } 5 | } 6 | 7 | trait OtherTrait { 8 | fn other_function(&self) -> bool { 9 | true 10 | } 11 | } 12 | 13 | struct SomeStruct; 14 | impl SomeTrait for SomeStruct {} 15 | impl OtherTrait for SomeStruct {} 16 | 17 | struct OtherStruct; 18 | impl SomeTrait for OtherStruct {} 19 | impl OtherTrait for OtherStruct {} 20 | 21 | // TODO: Fix the compiler error by only changing the signature of this function. 22 | fn some_func(item: ???) -> bool { 23 | item.some_function() && item.other_function() 24 | } 25 | 26 | fn main() { 27 | // You can optionally experiment here. 28 | } 29 | 30 | #[cfg(test)] 31 | mod tests { 32 | use super::*; 33 | 34 | #[test] 35 | fn test_some_func() { 36 | assert!(some_func(SomeStruct)); 37 | assert!(some_func(OtherStruct)); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /exercises/16_lifetimes/README.md: -------------------------------------------------------------------------------- 1 | # Lifetimes 2 | 3 | Lifetimes tell the compiler how to check whether references live long 4 | enough to be valid in any given situation. For example lifetimes say 5 | "make sure parameter 'a' lives as long as parameter 'b' so that the return 6 | value is valid". 7 | 8 | They are only necessary on borrows, i.e. references, 9 | since copied parameters or moves are owned in their scope and cannot 10 | be referenced outside. Lifetimes mean that calling code of e.g. functions 11 | can be checked to make sure their arguments are valid. Lifetimes are 12 | restrictive of their callers. 13 | 14 | If you'd like to learn more about lifetime annotations, the 15 | [lifetimekata](https://tfpk.github.io/lifetimekata/) project 16 | has a similar style of exercises to Rustlings, but is all about 17 | learning to write lifetime annotations. 18 | 19 | ## Further information 20 | 21 | - [Lifetimes (in Rust By Example)](https://doc.rust-lang.org/stable/rust-by-example/scope/lifetime.html) 22 | - [Validating References with Lifetimes](https://doc.rust-lang.org/book/ch10-03-lifetime-syntax.html) 23 | -------------------------------------------------------------------------------- /exercises/16_lifetimes/lifetimes1.rs: -------------------------------------------------------------------------------- 1 | // The Rust compiler needs to know how to check whether supplied references are 2 | // valid, so that it can let the programmer know if a reference is at risk of 3 | // going out of scope before it is used. Remember, references are borrows and do 4 | // not own their own data. What if their owner goes out of scope? 5 | 6 | // TODO: Fix the compiler error by updating the function signature. 7 | fn longest(x: &str, y: &str) -> &str { 8 | if x.len() > y.len() { 9 | x 10 | } else { 11 | y 12 | } 13 | } 14 | 15 | fn main() { 16 | // You can optionally experiment here. 17 | } 18 | 19 | #[cfg(test)] 20 | mod tests { 21 | use super::*; 22 | 23 | #[test] 24 | fn test_longest() { 25 | assert_eq!(longest("abcd", "123"), "abcd"); 26 | assert_eq!(longest("abc", "1234"), "1234"); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /exercises/16_lifetimes/lifetimes2.rs: -------------------------------------------------------------------------------- 1 | // Don't change this function. 2 | fn longest<'a>(x: &'a str, y: &'a str) -> &'a str { 3 | if x.len() > y.len() { 4 | x 5 | } else { 6 | y 7 | } 8 | } 9 | 10 | fn main() { 11 | // TODO: Fix the compiler error by moving one line. 12 | 13 | let string1 = String::from("long string is long"); 14 | let result; 15 | { 16 | let string2 = String::from("xyz"); 17 | result = longest(&string1, &string2); 18 | } 19 | println!("The longest string is '{result}'"); 20 | } 21 | -------------------------------------------------------------------------------- /exercises/16_lifetimes/lifetimes3.rs: -------------------------------------------------------------------------------- 1 | // Lifetimes are also needed when structs hold references. 2 | 3 | // TODO: Fix the compiler errors about the struct. 4 | struct Book { 5 | author: &str, 6 | title: &str, 7 | } 8 | 9 | fn main() { 10 | let book = Book { 11 | author: "George Orwell", 12 | title: "1984", 13 | }; 14 | 15 | println!("{} by {}", book.title, book.author); 16 | } 17 | -------------------------------------------------------------------------------- /exercises/17_tests/README.md: -------------------------------------------------------------------------------- 1 | # Tests 2 | 3 | Going out of order from the book to cover tests -- many of the following exercises will ask you to make tests pass! 4 | 5 | ## Further information 6 | 7 | - [Writing Tests](https://doc.rust-lang.org/book/ch11-01-writing-tests.html) 8 | -------------------------------------------------------------------------------- /exercises/17_tests/tests1.rs: -------------------------------------------------------------------------------- 1 | // Tests are important to ensure that your code does what you think it should 2 | // do. 3 | 4 | fn is_even(n: i64) -> bool { 5 | n % 2 == 0 6 | } 7 | 8 | fn main() { 9 | // You can optionally experiment here. 10 | } 11 | 12 | #[cfg(test)] 13 | mod tests { 14 | // TODO: Import `is_even`. You can use a wildcard to import everything in 15 | // the outer module. 16 | 17 | #[test] 18 | fn you_can_assert() { 19 | // TODO: Test the function `is_even` with some values. 20 | assert!(); 21 | assert!(); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /exercises/17_tests/tests2.rs: -------------------------------------------------------------------------------- 1 | // Calculates the power of 2 using a bit shift. 2 | // `1 << n` is equivalent to "2 to the power of n". 3 | fn power_of_2(n: u8) -> u64 { 4 | 1 << n 5 | } 6 | 7 | fn main() { 8 | // You can optionally experiment here. 9 | } 10 | 11 | #[cfg(test)] 12 | mod tests { 13 | use super::*; 14 | 15 | #[test] 16 | fn you_can_assert_eq() { 17 | // TODO: Test the function `power_of_2` with some values. 18 | assert_eq!(); 19 | assert_eq!(); 20 | assert_eq!(); 21 | assert_eq!(); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /exercises/17_tests/tests3.rs: -------------------------------------------------------------------------------- 1 | struct Rectangle { 2 | width: i32, 3 | height: i32, 4 | } 5 | 6 | impl Rectangle { 7 | // Don't change this function. 8 | fn new(width: i32, height: i32) -> Self { 9 | if width <= 0 || height <= 0 { 10 | // Returning a `Result` would be better here. But we want to learn 11 | // how to test functions that can panic. 12 | panic!("Rectangle width and height must be positive"); 13 | } 14 | 15 | Rectangle { width, height } 16 | } 17 | } 18 | 19 | fn main() { 20 | // You can optionally experiment here. 21 | } 22 | 23 | #[cfg(test)] 24 | mod tests { 25 | use super::*; 26 | 27 | #[test] 28 | fn correct_width_and_height() { 29 | // TODO: This test should check if the rectangle has the size that we 30 | // pass to its constructor. 31 | let rect = Rectangle::new(10, 20); 32 | assert_eq!(todo!(), 10); // Check width 33 | assert_eq!(todo!(), 20); // Check height 34 | } 35 | 36 | // TODO: This test should check if the program panics when we try to create 37 | // a rectangle with negative width. 38 | #[test] 39 | fn negative_width() { 40 | let _rect = Rectangle::new(-10, 10); 41 | } 42 | 43 | // TODO: This test should check if the program panics when we try to create 44 | // a rectangle with negative height. 45 | #[test] 46 | fn negative_height() { 47 | let _rect = Rectangle::new(10, -10); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /exercises/18_iterators/README.md: -------------------------------------------------------------------------------- 1 | # Iterators 2 | 3 | This section will teach you about Iterators. 4 | 5 | ## Further information 6 | 7 | - [Iterator](https://doc.rust-lang.org/book/ch13-02-iterators.html) 8 | - [Iterator documentation](https://doc.rust-lang.org/stable/std/iter/) 9 | -------------------------------------------------------------------------------- /exercises/18_iterators/iterators1.rs: -------------------------------------------------------------------------------- 1 | // When performing operations on elements within a collection, iterators are 2 | // essential. This module helps you get familiar with the structure of using an 3 | // iterator and how to go through elements within an iterable collection. 4 | 5 | fn main() { 6 | // You can optionally experiment here. 7 | } 8 | 9 | #[cfg(test)] 10 | mod tests { 11 | #[test] 12 | fn iterators() { 13 | let my_fav_fruits = ["banana", "custard apple", "avocado", "peach", "raspberry"]; 14 | 15 | // TODO: Create an iterator over the array. 16 | let mut fav_fruits_iterator = todo!(); 17 | 18 | assert_eq!(fav_fruits_iterator.next(), Some(&"banana")); 19 | assert_eq!(fav_fruits_iterator.next(), todo!()); // TODO: Replace `todo!()` 20 | assert_eq!(fav_fruits_iterator.next(), Some(&"avocado")); 21 | assert_eq!(fav_fruits_iterator.next(), todo!()); // TODO: Replace `todo!()` 22 | assert_eq!(fav_fruits_iterator.next(), Some(&"raspberry")); 23 | assert_eq!(fav_fruits_iterator.next(), todo!()); // TODO: Replace `todo!()` 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /exercises/18_iterators/iterators2.rs: -------------------------------------------------------------------------------- 1 | // In this exercise, you'll learn some of the unique advantages that iterators 2 | // can offer. 3 | 4 | // TODO: Complete the `capitalize_first` function. 5 | // "hello" -> "Hello" 6 | fn capitalize_first(input: &str) -> String { 7 | let mut chars = input.chars(); 8 | match chars.next() { 9 | None => String::new(), 10 | Some(first) => todo!(), 11 | } 12 | } 13 | 14 | // TODO: Apply the `capitalize_first` function to a slice of string slices. 15 | // Return a vector of strings. 16 | // ["hello", "world"] -> ["Hello", "World"] 17 | fn capitalize_words_vector(words: &[&str]) -> Vec { 18 | // ??? 19 | } 20 | 21 | // TODO: Apply the `capitalize_first` function again to a slice of string 22 | // slices. Return a single string. 23 | // ["hello", " ", "world"] -> "Hello World" 24 | fn capitalize_words_string(words: &[&str]) -> String { 25 | // ??? 26 | } 27 | 28 | fn main() { 29 | // You can optionally experiment here. 30 | } 31 | 32 | #[cfg(test)] 33 | mod tests { 34 | use super::*; 35 | 36 | #[test] 37 | fn test_success() { 38 | assert_eq!(capitalize_first("hello"), "Hello"); 39 | } 40 | 41 | #[test] 42 | fn test_empty() { 43 | assert_eq!(capitalize_first(""), ""); 44 | } 45 | 46 | #[test] 47 | fn test_iterate_string_vec() { 48 | let words = vec!["hello", "world"]; 49 | assert_eq!(capitalize_words_vector(&words), ["Hello", "World"]); 50 | } 51 | 52 | #[test] 53 | fn test_iterate_into_string() { 54 | let words = vec!["hello", " ", "world"]; 55 | assert_eq!(capitalize_words_string(&words), "Hello World"); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /exercises/18_iterators/iterators3.rs: -------------------------------------------------------------------------------- 1 | #[derive(Debug, PartialEq, Eq)] 2 | enum DivisionError { 3 | // Example: 42 / 0 4 | DivideByZero, 5 | // Only case for `i64`: `i64::MIN / -1` because the result is `i64::MAX + 1` 6 | IntegerOverflow, 7 | // Example: 5 / 2 = 2.5 8 | NotDivisible, 9 | } 10 | 11 | // TODO: Calculate `a` divided by `b` if `a` is evenly divisible by `b`. 12 | // Otherwise, return a suitable error. 13 | fn divide(a: i64, b: i64) -> Result { 14 | todo!(); 15 | } 16 | 17 | // TODO: Add the correct return type and complete the function body. 18 | // Desired output: `Ok([1, 11, 1426, 3])` 19 | fn result_with_list() { 20 | let numbers = [27, 297, 38502, 81]; 21 | let division_results = numbers.into_iter().map(|n| divide(n, 27)); 22 | } 23 | 24 | // TODO: Add the correct return type and complete the function body. 25 | // Desired output: `[Ok(1), Ok(11), Ok(1426), Ok(3)]` 26 | fn list_of_results() { 27 | let numbers = [27, 297, 38502, 81]; 28 | let division_results = numbers.into_iter().map(|n| divide(n, 27)); 29 | } 30 | 31 | fn main() { 32 | // You can optionally experiment here. 33 | } 34 | 35 | #[cfg(test)] 36 | mod tests { 37 | use super::*; 38 | 39 | #[test] 40 | fn test_success() { 41 | assert_eq!(divide(81, 9), Ok(9)); 42 | } 43 | 44 | #[test] 45 | fn test_divide_by_0() { 46 | assert_eq!(divide(81, 0), Err(DivisionError::DivideByZero)); 47 | } 48 | 49 | #[test] 50 | fn test_integer_overflow() { 51 | assert_eq!(divide(i64::MIN, -1), Err(DivisionError::IntegerOverflow)); 52 | } 53 | 54 | #[test] 55 | fn test_not_divisible() { 56 | assert_eq!(divide(81, 6), Err(DivisionError::NotDivisible)); 57 | } 58 | 59 | #[test] 60 | fn test_divide_0_by_something() { 61 | assert_eq!(divide(0, 81), Ok(0)); 62 | } 63 | 64 | #[test] 65 | fn test_result_with_list() { 66 | assert_eq!(result_with_list().unwrap(), [1, 11, 1426, 3]); 67 | } 68 | 69 | #[test] 70 | fn test_list_of_results() { 71 | assert_eq!(list_of_results(), [Ok(1), Ok(11), Ok(1426), Ok(3)]); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /exercises/18_iterators/iterators4.rs: -------------------------------------------------------------------------------- 1 | fn factorial(num: u64) -> u64 { 2 | // TODO: Complete this function to return the factorial of `num` which is 3 | // defined as `1 * 2 * 3 * … * num`. 4 | // https://en.wikipedia.org/wiki/Factorial 5 | // 6 | // Do not use: 7 | // - early returns (using the `return` keyword explicitly) 8 | // Try not to use: 9 | // - imperative style loops (for/while) 10 | // - additional variables 11 | // For an extra challenge, don't use: 12 | // - recursion 13 | } 14 | 15 | fn main() { 16 | // You can optionally experiment here. 17 | } 18 | 19 | #[cfg(test)] 20 | mod tests { 21 | use super::*; 22 | 23 | #[test] 24 | fn factorial_of_0() { 25 | assert_eq!(factorial(0), 1); 26 | } 27 | 28 | #[test] 29 | fn factorial_of_1() { 30 | assert_eq!(factorial(1), 1); 31 | } 32 | #[test] 33 | fn factorial_of_2() { 34 | assert_eq!(factorial(2), 2); 35 | } 36 | 37 | #[test] 38 | fn factorial_of_4() { 39 | assert_eq!(factorial(4), 24); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /exercises/19_smart_pointers/README.md: -------------------------------------------------------------------------------- 1 | # Smart Pointers 2 | 3 | In Rust, smart pointers are variables that contain an address in memory and reference some other data, but they also have additional metadata and capabilities. 4 | Smart pointers in Rust often own the data they point to, while references only borrow data. 5 | 6 | ## Further Information 7 | 8 | - [Smart Pointers](https://doc.rust-lang.org/book/ch15-00-smart-pointers.html) 9 | - [Using Box to Point to Data on the Heap](https://doc.rust-lang.org/book/ch15-01-box.html) 10 | - [Rc\, the Reference Counted Smart Pointer](https://doc.rust-lang.org/book/ch15-04-rc.html) 11 | - [Shared-State Concurrency](https://doc.rust-lang.org/book/ch16-03-shared-state.html) 12 | - [Cow Documentation](https://doc.rust-lang.org/std/borrow/enum.Cow.html) 13 | -------------------------------------------------------------------------------- /exercises/19_smart_pointers/arc1.rs: -------------------------------------------------------------------------------- 1 | // In this exercise, we are given a `Vec` of `u32` called `numbers` with values 2 | // ranging from 0 to 99. We would like to use this set of numbers within 8 3 | // different threads simultaneously. Each thread is going to get the sum of 4 | // every eighth value with an offset. 5 | // 6 | // The first thread (offset 0), will sum 0, 8, 16, … 7 | // The second thread (offset 1), will sum 1, 9, 17, … 8 | // The third thread (offset 2), will sum 2, 10, 18, … 9 | // … 10 | // The eighth thread (offset 7), will sum 7, 15, 23, … 11 | // 12 | // Each thread should own a reference-counting pointer to the vector of 13 | // numbers. But `Rc` isn't thread-safe. Therefore, we need to use `Arc`. 14 | // 15 | // Don't get distracted by how threads are spawned and joined. We will practice 16 | // that later in the exercises about threads. 17 | 18 | // Don't change the lines below. 19 | #![forbid(unused_imports)] 20 | use std::{sync::Arc, thread}; 21 | 22 | fn main() { 23 | let numbers: Vec<_> = (0..100u32).collect(); 24 | 25 | // TODO: Define `shared_numbers` by using `Arc`. 26 | // let shared_numbers = ???; 27 | 28 | let mut join_handles = Vec::new(); 29 | 30 | for offset in 0..8 { 31 | // TODO: Define `child_numbers` using `shared_numbers`. 32 | // let child_numbers = ???; 33 | 34 | let handle = thread::spawn(move || { 35 | let sum: u32 = child_numbers.iter().filter(|&&n| n % 8 == offset).sum(); 36 | println!("Sum of offset {offset} is {sum}"); 37 | }); 38 | 39 | join_handles.push(handle); 40 | } 41 | 42 | for handle in join_handles.into_iter() { 43 | handle.join().unwrap(); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /exercises/19_smart_pointers/box1.rs: -------------------------------------------------------------------------------- 1 | // At compile time, Rust needs to know how much space a type takes up. This 2 | // becomes problematic for recursive types, where a value can have as part of 3 | // itself another value of the same type. To get around the issue, we can use a 4 | // `Box` - a smart pointer used to store data on the heap, which also allows us 5 | // to wrap a recursive type. 6 | // 7 | // The recursive type we're implementing in this exercise is the "cons list", a 8 | // data structure frequently found in functional programming languages. Each 9 | // item in a cons list contains two elements: The value of the current item and 10 | // the next item. The last item is a value called `Nil`. 11 | 12 | // TODO: Use a `Box` in the enum definition to make the code compile. 13 | #[derive(PartialEq, Debug)] 14 | enum List { 15 | Cons(i32, List), 16 | Nil, 17 | } 18 | 19 | // TODO: Create an empty cons list. 20 | fn create_empty_list() -> List { 21 | todo!() 22 | } 23 | 24 | // TODO: Create a non-empty cons list. 25 | fn create_non_empty_list() -> List { 26 | todo!() 27 | } 28 | 29 | fn main() { 30 | println!("This is an empty cons list: {:?}", create_empty_list()); 31 | println!( 32 | "This is a non-empty cons list: {:?}", 33 | create_non_empty_list(), 34 | ); 35 | } 36 | 37 | #[cfg(test)] 38 | mod tests { 39 | use super::*; 40 | 41 | #[test] 42 | fn test_create_empty_list() { 43 | assert_eq!(create_empty_list(), List::Nil); 44 | } 45 | 46 | #[test] 47 | fn test_create_non_empty_list() { 48 | assert_ne!(create_empty_list(), create_non_empty_list()); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /exercises/19_smart_pointers/cow1.rs: -------------------------------------------------------------------------------- 1 | // This exercise explores the `Cow` (Clone-On-Write) smart pointer. It can 2 | // enclose and provide immutable access to borrowed data and clone the data 3 | // lazily when mutation or ownership is required. The type is designed to work 4 | // with general borrowed data via the `Borrow` trait. 5 | 6 | use std::borrow::Cow; 7 | 8 | fn abs_all(input: &mut Cow<[i32]>) { 9 | for ind in 0..input.len() { 10 | let value = input[ind]; 11 | if value < 0 { 12 | // Clones into a vector if not already owned. 13 | input.to_mut()[ind] = -value; 14 | } 15 | } 16 | } 17 | 18 | fn main() { 19 | // You can optionally experiment here. 20 | } 21 | 22 | #[cfg(test)] 23 | mod tests { 24 | use super::*; 25 | 26 | #[test] 27 | fn reference_mutation() { 28 | // Clone occurs because `input` needs to be mutated. 29 | let vec = vec![-1, 0, 1]; 30 | let mut input = Cow::from(&vec); 31 | abs_all(&mut input); 32 | assert!(matches!(input, Cow::Owned(_))); 33 | } 34 | 35 | #[test] 36 | fn reference_no_mutation() { 37 | // No clone occurs because `input` doesn't need to be mutated. 38 | let vec = vec![0, 1, 2]; 39 | let mut input = Cow::from(&vec); 40 | abs_all(&mut input); 41 | // TODO: Replace `todo!()` with `Cow::Owned(_)` or `Cow::Borrowed(_)`. 42 | assert!(matches!(input, todo!())); 43 | } 44 | 45 | #[test] 46 | fn owned_no_mutation() { 47 | // We can also pass `vec` without `&` so `Cow` owns it directly. In this 48 | // case, no mutation occurs (all numbers are already absolute) and thus 49 | // also no clone. But the result is still owned because it was never 50 | // borrowed or mutated. 51 | let vec = vec![0, 1, 2]; 52 | let mut input = Cow::from(vec); 53 | abs_all(&mut input); 54 | // TODO: Replace `todo!()` with `Cow::Owned(_)` or `Cow::Borrowed(_)`. 55 | assert!(matches!(input, todo!())); 56 | } 57 | 58 | #[test] 59 | fn owned_mutation() { 60 | // Of course this is also the case if a mutation does occur (not all 61 | // numbers are absolute). In this case, the call to `to_mut()` in the 62 | // `abs_all` function returns a reference to the same data as before. 63 | let vec = vec![-1, 0, 1]; 64 | let mut input = Cow::from(vec); 65 | abs_all(&mut input); 66 | // TODO: Replace `todo!()` with `Cow::Owned(_)` or `Cow::Borrowed(_)`. 67 | assert!(matches!(input, todo!())); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /exercises/20_threads/README.md: -------------------------------------------------------------------------------- 1 | # Threads 2 | 3 | In most current operating systems, an executed program's code is run in a process, and the operating system manages multiple processes at once. 4 | Within your program, you can also have independent parts that run simultaneously. The features that run these independent parts are called threads. 5 | 6 | ## Further information 7 | 8 | - [Dining Philosophers example](https://doc.rust-lang.org/1.4.0/book/dining-philosophers.html) 9 | - [Using Threads to Run Code Simultaneously](https://doc.rust-lang.org/book/ch16-01-threads.html) 10 | - [Using Message Passing to Transfer Data Between Threads](https://doc.rust-lang.org/book/ch16-02-message-passing.html) 11 | -------------------------------------------------------------------------------- /exercises/20_threads/threads1.rs: -------------------------------------------------------------------------------- 1 | // This program spawns multiple threads that each runs for at least 250ms, and 2 | // each thread returns how much time it took to complete. The program should 3 | // wait until all the spawned threads have finished and should collect their 4 | // return values into a vector. 5 | 6 | use std::{ 7 | thread, 8 | time::{Duration, Instant}, 9 | }; 10 | 11 | fn main() { 12 | let mut handles = Vec::new(); 13 | for i in 0..10 { 14 | let handle = thread::spawn(move || { 15 | let start = Instant::now(); 16 | thread::sleep(Duration::from_millis(250)); 17 | println!("Thread {i} done"); 18 | start.elapsed().as_millis() 19 | }); 20 | handles.push(handle); 21 | } 22 | 23 | let mut results = Vec::new(); 24 | for handle in handles { 25 | // TODO: Collect the results of all threads into the `results` vector. 26 | // Use the `JoinHandle` struct which is returned by `thread::spawn`. 27 | } 28 | 29 | if results.len() != 10 { 30 | panic!("Oh no! Some thread isn't done yet!"); 31 | } 32 | 33 | println!(); 34 | for (i, result) in results.into_iter().enumerate() { 35 | println!("Thread {i} took {result}ms"); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /exercises/20_threads/threads2.rs: -------------------------------------------------------------------------------- 1 | // Building on the last exercise, we want all of the threads to complete their 2 | // work. But this time, the spawned threads need to be in charge of updating a 3 | // shared value: `JobStatus.jobs_done` 4 | 5 | use std::{sync::Arc, thread, time::Duration}; 6 | 7 | struct JobStatus { 8 | jobs_done: u32, 9 | } 10 | 11 | fn main() { 12 | // TODO: `Arc` isn't enough if you want a **mutable** shared state. 13 | let status = Arc::new(JobStatus { jobs_done: 0 }); 14 | 15 | let mut handles = Vec::new(); 16 | for _ in 0..10 { 17 | let status_shared = Arc::clone(&status); 18 | let handle = thread::spawn(move || { 19 | thread::sleep(Duration::from_millis(250)); 20 | 21 | // TODO: You must take an action before you update a shared value. 22 | status_shared.jobs_done += 1; 23 | }); 24 | handles.push(handle); 25 | } 26 | 27 | // Waiting for all jobs to complete. 28 | for handle in handles { 29 | handle.join().unwrap(); 30 | } 31 | 32 | // TODO: Print the value of `JobStatus.jobs_done`. 33 | println!("Jobs done: {}", todo!()); 34 | } 35 | -------------------------------------------------------------------------------- /exercises/20_threads/threads3.rs: -------------------------------------------------------------------------------- 1 | use std::{sync::mpsc, thread, time::Duration}; 2 | 3 | struct Queue { 4 | first_half: Vec, 5 | second_half: Vec, 6 | } 7 | 8 | impl Queue { 9 | fn new() -> Self { 10 | Self { 11 | first_half: vec![1, 2, 3, 4, 5], 12 | second_half: vec![6, 7, 8, 9, 10], 13 | } 14 | } 15 | } 16 | 17 | fn send_tx(q: Queue, tx: mpsc::Sender) { 18 | // TODO: We want to send `tx` to both threads. But currently, it is moved 19 | // into the first thread. How could you solve this problem? 20 | thread::spawn(move || { 21 | for val in q.first_half { 22 | println!("Sending {val:?}"); 23 | tx.send(val).unwrap(); 24 | thread::sleep(Duration::from_millis(250)); 25 | } 26 | }); 27 | 28 | thread::spawn(move || { 29 | for val in q.second_half { 30 | println!("Sending {val:?}"); 31 | tx.send(val).unwrap(); 32 | thread::sleep(Duration::from_millis(250)); 33 | } 34 | }); 35 | } 36 | 37 | fn main() { 38 | // You can optionally experiment here. 39 | } 40 | 41 | #[cfg(test)] 42 | mod tests { 43 | use super::*; 44 | 45 | #[test] 46 | fn threads3() { 47 | let (tx, rx) = mpsc::channel(); 48 | let queue = Queue::new(); 49 | 50 | send_tx(queue, tx); 51 | 52 | let mut received = Vec::with_capacity(10); 53 | for value in rx { 54 | received.push(value); 55 | } 56 | 57 | received.sort(); 58 | assert_eq!(received, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /exercises/21_macros/README.md: -------------------------------------------------------------------------------- 1 | # Macros 2 | 3 | Rust's macro system is very powerful, but also kind of difficult to wrap your 4 | head around. We're not going to teach you how to write your own fully-featured 5 | macros. Instead, we'll show you how to use and create them. 6 | 7 | If you'd like to learn more about writing your own macros, the 8 | [macrokata](https://github.com/tfpk/macrokata) project has a similar style 9 | of exercises to Rustlings, but is all about learning to write Macros. 10 | 11 | ## Further information 12 | 13 | - [The Rust Book - Macros](https://doc.rust-lang.org/book/ch20-05-macros.html) 14 | - [The Little Book of Rust Macros](https://veykril.github.io/tlborm/) 15 | - [Rust by Example - macro_rules!](https://doc.rust-lang.org/rust-by-example/macros.html) 16 | -------------------------------------------------------------------------------- /exercises/21_macros/macros1.rs: -------------------------------------------------------------------------------- 1 | macro_rules! my_macro { 2 | () => { 3 | println!("Check out my macro!"); 4 | }; 5 | } 6 | 7 | fn main() { 8 | // TODO: Fix the macro call. 9 | my_macro(); 10 | } 11 | -------------------------------------------------------------------------------- /exercises/21_macros/macros2.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | my_macro!(); 3 | } 4 | 5 | // TODO: Fix the compiler error by moving the whole definition of this macro. 6 | macro_rules! my_macro { 7 | () => { 8 | println!("Check out my macro!"); 9 | }; 10 | } 11 | -------------------------------------------------------------------------------- /exercises/21_macros/macros3.rs: -------------------------------------------------------------------------------- 1 | // TODO: Fix the compiler error without taking the macro definition out of this 2 | // module. 3 | mod macros { 4 | macro_rules! my_macro { 5 | () => { 6 | println!("Check out my macro!"); 7 | }; 8 | } 9 | } 10 | 11 | fn main() { 12 | my_macro!(); 13 | } 14 | -------------------------------------------------------------------------------- /exercises/21_macros/macros4.rs: -------------------------------------------------------------------------------- 1 | // TODO: Fix the compiler error by adding one or two characters. 2 | #[rustfmt::skip] 3 | macro_rules! my_macro { 4 | () => { 5 | println!("Check out my macro!"); 6 | } 7 | ($val:expr) => { 8 | println!("Look at this other macro: {}", $val); 9 | } 10 | } 11 | 12 | fn main() { 13 | my_macro!(); 14 | my_macro!(7777); 15 | } 16 | -------------------------------------------------------------------------------- /exercises/22_clippy/README.md: -------------------------------------------------------------------------------- 1 | # Clippy 2 | 3 | The Clippy tool is a collection of lints to analyze your code so you can catch common mistakes and improve your Rust code. 4 | 5 | If you used the installation script for Rustlings, Clippy should be already installed. 6 | If not you can install it manually via `rustup component add clippy`. 7 | 8 | ## Further information 9 | 10 | - [GitHub Repository](https://github.com/rust-lang/rust-clippy). 11 | -------------------------------------------------------------------------------- /exercises/22_clippy/clippy1.rs: -------------------------------------------------------------------------------- 1 | // The Clippy tool is a collection of lints to analyze your code so you can 2 | // catch common mistakes and improve your Rust code. 3 | // 4 | // For these exercises, the code will fail to compile when there are Clippy 5 | // warnings. Check Clippy's suggestions from the output to solve the exercise. 6 | 7 | fn main() { 8 | // TODO: Fix the Clippy lint in this line. 9 | let pi = 3.14; 10 | let radius: f32 = 5.0; 11 | 12 | let area = pi * radius.powi(2); 13 | 14 | println!("The area of a circle with radius {radius:.2} is {area:.5}"); 15 | } 16 | -------------------------------------------------------------------------------- /exercises/22_clippy/clippy2.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | let mut res = 42; 3 | let option = Some(12); 4 | // TODO: Fix the Clippy lint. 5 | for x in option { 6 | res += x; 7 | } 8 | 9 | println!("{res}"); 10 | } 11 | -------------------------------------------------------------------------------- /exercises/22_clippy/clippy3.rs: -------------------------------------------------------------------------------- 1 | // Here are some more easy Clippy fixes so you can see its utility 📎 2 | // TODO: Fix all the Clippy lints. 3 | 4 | #[rustfmt::skip] 5 | #[allow(unused_variables, unused_assignments)] 6 | fn main() { 7 | let my_option: Option<&str> = None; 8 | // Assume that you don't know the value of `my_option`. 9 | // In the case of `Some`, we want to print its value. 10 | if my_option.is_none() { 11 | println!("{}", my_option.unwrap()); 12 | } 13 | 14 | let my_arr = &[ 15 | -1, -2, -3 16 | -4, -5, -6 17 | ]; 18 | println!("My array! Here it is: {my_arr:?}"); 19 | 20 | let my_empty_vec = vec![1, 2, 3, 4, 5].resize(0, 5); 21 | println!("This Vec is empty, see? {my_empty_vec:?}"); 22 | 23 | let mut value_a = 45; 24 | let mut value_b = 66; 25 | // Let's swap these two! 26 | value_a = value_b; 27 | value_b = value_a; 28 | println!("value a: {value_a}; value b: {value_b}"); 29 | } 30 | -------------------------------------------------------------------------------- /exercises/23_conversions/README.md: -------------------------------------------------------------------------------- 1 | # Type conversions 2 | 3 | Rust offers a multitude of ways to convert a value of a given type into another type. 4 | 5 | The simplest form of type conversion is a type cast expression. It is denoted with the binary operator `as`. For instance, `println!("{}", 1 + 1.0);` would not compile, since `1` is an integer while `1.0` is a float. However, `println!("{}", 1 as f32 + 1.0)` should compile. The exercise [`using_as`](using_as.rs) tries to cover this. 6 | 7 | Rust also offers traits that facilitate type conversions upon implementation. These traits can be found under the [`convert`](https://doc.rust-lang.org/std/convert/index.html) module. 8 | The traits are the following: 9 | 10 | - `From` and `Into` covered in [`from_into`](from_into.rs) 11 | - `TryFrom` and `TryInto` covered in [`try_from_into`](try_from_into.rs) 12 | - `AsRef` and `AsMut` covered in [`as_ref_mut`](as_ref_mut.rs) 13 | 14 | Furthermore, the `std::str` module offers a trait called [`FromStr`](https://doc.rust-lang.org/std/str/trait.FromStr.html) which helps with converting strings into target types via the `parse` method on strings. If properly implemented for a given type `Person`, then `let p: Person = "Mark,20".parse().unwrap()` should both compile and run without panicking. 15 | 16 | These should be the main ways ***within the standard library*** to convert data into your desired types. 17 | 18 | ## Further information 19 | 20 | These are not directly covered in the book, but the standard library has a great documentation for it. 21 | 22 | - [conversions](https://doc.rust-lang.org/std/convert/index.html) 23 | - [`FromStr` trait](https://doc.rust-lang.org/std/str/trait.FromStr.html) 24 | -------------------------------------------------------------------------------- /exercises/23_conversions/as_ref_mut.rs: -------------------------------------------------------------------------------- 1 | // AsRef and AsMut allow for cheap reference-to-reference conversions. Read more 2 | // about them at https://doc.rust-lang.org/std/convert/trait.AsRef.html and 3 | // https://doc.rust-lang.org/std/convert/trait.AsMut.html, respectively. 4 | 5 | // Obtain the number of bytes (not characters) in the given argument 6 | // (`.len()` returns the number of bytes in a string). 7 | // TODO: Add the `AsRef` trait appropriately as a trait bound. 8 | fn byte_counter(arg: T) -> usize { 9 | arg.as_ref().len() 10 | } 11 | 12 | // Obtain the number of characters (not bytes) in the given argument. 13 | // TODO: Add the `AsRef` trait appropriately as a trait bound. 14 | fn char_counter(arg: T) -> usize { 15 | arg.as_ref().chars().count() 16 | } 17 | 18 | // Squares a number using `as_mut()`. 19 | // TODO: Add the appropriate trait bound. 20 | fn num_sq(arg: &mut T) { 21 | // TODO: Implement the function body. 22 | } 23 | 24 | fn main() { 25 | // You can optionally experiment here. 26 | } 27 | 28 | #[cfg(test)] 29 | mod tests { 30 | use super::*; 31 | 32 | #[test] 33 | fn different_counts() { 34 | let s = "Café au lait"; 35 | assert_ne!(char_counter(s), byte_counter(s)); 36 | } 37 | 38 | #[test] 39 | fn same_counts() { 40 | let s = "Cafe au lait"; 41 | assert_eq!(char_counter(s), byte_counter(s)); 42 | } 43 | 44 | #[test] 45 | fn different_counts_using_string() { 46 | let s = String::from("Café au lait"); 47 | assert_ne!(char_counter(s.clone()), byte_counter(s)); 48 | } 49 | 50 | #[test] 51 | fn same_counts_using_string() { 52 | let s = String::from("Cafe au lait"); 53 | assert_eq!(char_counter(s.clone()), byte_counter(s)); 54 | } 55 | 56 | #[test] 57 | fn mut_box() { 58 | let mut num: Box = Box::new(3); 59 | num_sq(&mut num); 60 | assert_eq!(*num, 9); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /exercises/23_conversions/using_as.rs: -------------------------------------------------------------------------------- 1 | // Type casting in Rust is done via the usage of the `as` operator. 2 | // Note that the `as` operator is not only used when type casting. It also helps 3 | // with renaming imports. 4 | 5 | fn average(values: &[f64]) -> f64 { 6 | let total = values.iter().sum::(); 7 | // TODO: Make a conversion before dividing. 8 | total / values.len() 9 | } 10 | 11 | fn main() { 12 | let values = [3.5, 0.3, 13.0, 11.7]; 13 | println!("{}", average(&values)); 14 | } 15 | 16 | #[cfg(test)] 17 | mod tests { 18 | use super::*; 19 | 20 | #[test] 21 | fn returns_proper_type_and_value() { 22 | assert_eq!(average(&[3.5, 0.3, 13.0, 11.7]), 7.125); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /exercises/README.md: -------------------------------------------------------------------------------- 1 | # Exercise to Book Chapter mapping 2 | 3 | | Exercise | Book Chapter | 4 | | ---------------------- | ------------------- | 5 | | variables | §3.1 | 6 | | functions | §3.3 | 7 | | if | §3.5 | 8 | | primitive_types | §3.2, §4.3 | 9 | | vecs | §8.1 | 10 | | move_semantics | §4.1-2 | 11 | | structs | §5.1, §5.3 | 12 | | enums | §6, §18.3 | 13 | | strings | §8.2 | 14 | | modules | §7 | 15 | | hashmaps | §8.3 | 16 | | options | §10.1 | 17 | | error_handling | §9 | 18 | | generics | §10 | 19 | | traits | §10.2 | 20 | | lifetimes | §10.3 | 21 | | tests | §11.1 | 22 | | iterators | §13.2-4 | 23 | | smart_pointers | §15, §16.3 | 24 | | threads | §16.1-3 | 25 | | macros | §19.5 | 26 | | clippy | §21.4 | 27 | | conversions | n/a | 28 | -------------------------------------------------------------------------------- /exercises/quizzes/README.md: -------------------------------------------------------------------------------- 1 | # Quizzes 2 | 3 | After every couple of sections, there will be a quiz in this directory that'll test your knowledge on a bunch of sections at once. 4 | -------------------------------------------------------------------------------- /exercises/quizzes/quiz1.rs: -------------------------------------------------------------------------------- 1 | // This is a quiz for the following sections: 2 | // - Variables 3 | // - Functions 4 | // - If 5 | // 6 | // Mary is buying apples. The price of an apple is calculated as follows: 7 | // - An apple costs 2 rustbucks. 8 | // - However, if Mary buys more than 40 apples, the price of each apple in the 9 | // entire order is reduced to only 1 rustbuck! 10 | 11 | // TODO: Write a function that calculates the price of an order of apples given 12 | // the quantity bought. 13 | // fn calculate_price_of_apples(???) -> ??? { ??? } 14 | 15 | fn main() { 16 | // You can optionally experiment here. 17 | } 18 | 19 | // Don't change the tests! 20 | #[cfg(test)] 21 | mod tests { 22 | use super::*; 23 | 24 | #[test] 25 | fn verify_test() { 26 | assert_eq!(calculate_price_of_apples(35), 70); 27 | assert_eq!(calculate_price_of_apples(40), 80); 28 | assert_eq!(calculate_price_of_apples(41), 41); 29 | assert_eq!(calculate_price_of_apples(65), 65); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /exercises/quizzes/quiz2.rs: -------------------------------------------------------------------------------- 1 | // This is a quiz for the following sections: 2 | // - Strings 3 | // - Vecs 4 | // - Move semantics 5 | // - Modules 6 | // - Enums 7 | // 8 | // Let's build a little machine in the form of a function. As input, we're going 9 | // to give a list of strings and commands. These commands determine what action 10 | // is going to be applied to the string. It can either be: 11 | // - Uppercase the string 12 | // - Trim the string 13 | // - Append "bar" to the string a specified amount of times 14 | // 15 | // The exact form of this will be: 16 | // - The input is going to be a Vector of 2-length tuples, 17 | // the first element is the string, the second one is the command. 18 | // - The output element is going to be a vector of strings. 19 | 20 | enum Command { 21 | Uppercase, 22 | Trim, 23 | Append(usize), 24 | } 25 | 26 | mod my_module { 27 | use super::Command; 28 | 29 | // TODO: Complete the function as described above. 30 | // pub fn transformer(input: ???) -> ??? { ??? } 31 | } 32 | 33 | fn main() { 34 | // You can optionally experiment here. 35 | } 36 | 37 | #[cfg(test)] 38 | mod tests { 39 | // TODO: What do we need to import to have `transformer` in scope? 40 | // use ???; 41 | use super::Command; 42 | 43 | #[test] 44 | fn it_works() { 45 | let input = vec![ 46 | ("hello".to_string(), Command::Uppercase), 47 | (" all roads lead to rome! ".to_string(), Command::Trim), 48 | ("foo".to_string(), Command::Append(1)), 49 | ("bar".to_string(), Command::Append(5)), 50 | ]; 51 | let output = transformer(input); 52 | 53 | assert_eq!( 54 | output, 55 | [ 56 | "HELLO", 57 | "all roads lead to rome!", 58 | "foobar", 59 | "barbarbarbarbarbar", 60 | ] 61 | ); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /exercises/quizzes/quiz3.rs: -------------------------------------------------------------------------------- 1 | // This quiz tests: 2 | // - Generics 3 | // - Traits 4 | // 5 | // An imaginary magical school has a new report card generation system written 6 | // in Rust! Currently, the system only supports creating report cards where the 7 | // student's grade is represented numerically (e.g. 1.0 -> 5.5). However, the 8 | // school also issues alphabetical grades (A+ -> F-) and needs to be able to 9 | // print both types of report card! 10 | // 11 | // Make the necessary code changes in the struct `ReportCard` and the impl 12 | // block to support alphabetical report cards in addition to numerical ones. 13 | 14 | // TODO: Adjust the struct as described above. 15 | struct ReportCard { 16 | grade: f32, 17 | student_name: String, 18 | student_age: u8, 19 | } 20 | 21 | // TODO: Adjust the impl block as described above. 22 | impl ReportCard { 23 | fn print(&self) -> String { 24 | format!( 25 | "{} ({}) - achieved a grade of {}", 26 | &self.student_name, &self.student_age, &self.grade, 27 | ) 28 | } 29 | } 30 | 31 | fn main() { 32 | // You can optionally experiment here. 33 | } 34 | 35 | #[cfg(test)] 36 | mod tests { 37 | use super::*; 38 | 39 | #[test] 40 | fn generate_numeric_report_card() { 41 | let report_card = ReportCard { 42 | grade: 2.1, 43 | student_name: "Tom Wriggle".to_string(), 44 | student_age: 12, 45 | }; 46 | assert_eq!( 47 | report_card.print(), 48 | "Tom Wriggle (12) - achieved a grade of 2.1", 49 | ); 50 | } 51 | 52 | #[test] 53 | fn generate_alphabetic_report_card() { 54 | let report_card = ReportCard { 55 | grade: "A+", 56 | student_name: "Gary Plotter".to_string(), 57 | student_age: 11, 58 | }; 59 | assert_eq!( 60 | report_card.print(), 61 | "Gary Plotter (11) - achieved a grade of A+", 62 | ); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /release-hook.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Error out if any command fails 4 | set -e 5 | 6 | typos 7 | cargo upgrades 8 | 9 | # Similar to CI 10 | cargo clippy -- --deny warnings 11 | cargo fmt --all --check 12 | cargo test --workspace 13 | cargo dev check --require-solutions 14 | 15 | # MSRV 16 | cargo +1.87 dev check --require-solutions 17 | -------------------------------------------------------------------------------- /rustlings-macros/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rustlings-macros" 3 | description = "A macros crate intended to be used only by Rustlings" 4 | version.workspace = true 5 | authors.workspace = true 6 | repository.workspace = true 7 | license.workspace = true 8 | edition.workspace = true 9 | rust-version.workspace = true 10 | include = [ 11 | "/src/", 12 | "/info.toml", 13 | ] 14 | 15 | [lib] 16 | proc-macro = true 17 | 18 | [dependencies] 19 | quote = "1.0" 20 | serde.workspace = true 21 | toml_edit.workspace = true 22 | 23 | [lints] 24 | workspace = true 25 | -------------------------------------------------------------------------------- /rustlings-macros/src/lib.rs: -------------------------------------------------------------------------------- 1 | use proc_macro::TokenStream; 2 | use quote::quote; 3 | use serde::Deserialize; 4 | 5 | #[derive(Deserialize)] 6 | struct ExerciseInfo { 7 | name: String, 8 | dir: String, 9 | } 10 | 11 | #[derive(Deserialize)] 12 | struct InfoFile { 13 | exercises: Vec, 14 | } 15 | 16 | #[proc_macro] 17 | pub fn include_files(_: TokenStream) -> TokenStream { 18 | let info_file = include_str!("../info.toml"); 19 | let exercises = toml_edit::de::from_str::(info_file) 20 | .expect("Failed to parse `info.toml`") 21 | .exercises; 22 | 23 | let exercise_files = exercises 24 | .iter() 25 | .map(|exercise| format!("../exercises/{}/{}.rs", exercise.dir, exercise.name)); 26 | let solution_files = exercises 27 | .iter() 28 | .map(|exercise| format!("../solutions/{}/{}.rs", exercise.dir, exercise.name)); 29 | 30 | let mut dirs = Vec::with_capacity(32); 31 | let mut dir_inds = vec![0; exercises.len()]; 32 | 33 | for (exercise, dir_ind) in exercises.iter().zip(&mut dir_inds) { 34 | // The directory is often the last one inserted. 35 | if let Some(ind) = dirs.iter().rev().position(|dir| *dir == exercise.dir) { 36 | *dir_ind = dirs.len() - 1 - ind; 37 | continue; 38 | } 39 | 40 | dirs.push(exercise.dir.as_str()); 41 | *dir_ind = dirs.len() - 1; 42 | } 43 | 44 | let readmes = dirs 45 | .iter() 46 | .map(|dir| format!("../exercises/{dir}/README.md")); 47 | 48 | quote! { 49 | EmbeddedFiles { 50 | info_file: #info_file, 51 | exercise_files: &[#(ExerciseFiles { exercise: include_bytes!(#exercise_files), solution: include_bytes!(#solution_files), dir_ind: #dir_inds }),*], 52 | exercise_dirs: &[#(ExerciseDir { name: #dirs, readme: include_bytes!(#readmes) }),*] 53 | } 54 | } 55 | .into() 56 | } 57 | -------------------------------------------------------------------------------- /solutions/00_intro/intro1.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | // Congratulations, you finished the first exercise 🎉 3 | // As an introduction to Rustlings, the first exercise only required 4 | // entering `n` in the terminal to go to the next exercise. 5 | } 6 | -------------------------------------------------------------------------------- /solutions/00_intro/intro2.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | // `println!` instead of `printline!`. 3 | println!("Hello world!"); 4 | } 5 | -------------------------------------------------------------------------------- /solutions/01_variables/variables1.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | // Declaring variables requires the `let` keyword. 3 | let x = 5; 4 | 5 | println!("x has the value {x}"); 6 | } 7 | -------------------------------------------------------------------------------- /solutions/01_variables/variables2.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | // The easiest way to fix the compiler error is to initialize the 3 | // variable `x`. By setting its value to an integer, Rust infers its type 4 | // as `i32` which is the default type for integers. 5 | let x = 42; 6 | 7 | // But we can enforce a type different from the default `i32` by adding 8 | // a type annotation: 9 | // let x: u8 = 42; 10 | 11 | if x == 10 { 12 | println!("x is ten!"); 13 | } else { 14 | println!("x is not ten!"); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /solutions/01_variables/variables3.rs: -------------------------------------------------------------------------------- 1 | #![allow(clippy::needless_late_init)] 2 | 3 | fn main() { 4 | // Reading uninitialized variables isn't allowed in Rust! 5 | // Therefore, we need to assign a value first. 6 | let x: i32 = 42; 7 | 8 | println!("Number {x}"); 9 | 10 | // It is possible to declare a variable and initialize it later. 11 | // But it can't be used before initialization. 12 | let y: i32; 13 | y = 42; 14 | println!("Number {y}"); 15 | } 16 | -------------------------------------------------------------------------------- /solutions/01_variables/variables4.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | // In Rust, variables are immutable by default. 3 | // Adding the `mut` keyword after `let` makes the declared variable mutable. 4 | let mut x = 3; 5 | println!("Number {x}"); 6 | 7 | x = 5; 8 | println!("Number {x}"); 9 | } 10 | -------------------------------------------------------------------------------- /solutions/01_variables/variables5.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | let number = "T-H-R-E-E"; 3 | println!("Spell a number: {number}"); 4 | 5 | // Using variable shadowing 6 | // https://doc.rust-lang.org/book/ch03-01-variables-and-mutability.html#shadowing 7 | let number = 3; 8 | println!("Number plus two is: {}", number + 2); 9 | } 10 | -------------------------------------------------------------------------------- /solutions/01_variables/variables6.rs: -------------------------------------------------------------------------------- 1 | // The type of constants must always be annotated. 2 | const NUMBER: u64 = 3; 3 | 4 | fn main() { 5 | println!("Number: {NUMBER}"); 6 | } 7 | -------------------------------------------------------------------------------- /solutions/02_functions/functions1.rs: -------------------------------------------------------------------------------- 1 | // Some function with the name `call_me` without arguments or a return value. 2 | fn call_me() { 3 | println!("Hello world!"); 4 | } 5 | 6 | fn main() { 7 | call_me(); 8 | } 9 | -------------------------------------------------------------------------------- /solutions/02_functions/functions2.rs: -------------------------------------------------------------------------------- 1 | // The type of function arguments must be annotated. 2 | // Added the type annotation `u64`. 3 | fn call_me(num: u64) { 4 | for i in 0..num { 5 | println!("Ring! Call number {}", i + 1); 6 | } 7 | } 8 | 9 | fn main() { 10 | call_me(3); 11 | } 12 | -------------------------------------------------------------------------------- /solutions/02_functions/functions3.rs: -------------------------------------------------------------------------------- 1 | fn call_me(num: u8) { 2 | for i in 0..num { 3 | println!("Ring! Call number {}", i + 1); 4 | } 5 | } 6 | 7 | fn main() { 8 | // `call_me` expects an argument. 9 | call_me(5); 10 | } 11 | -------------------------------------------------------------------------------- /solutions/02_functions/functions4.rs: -------------------------------------------------------------------------------- 1 | fn is_even(num: i64) -> bool { 2 | num % 2 == 0 3 | } 4 | 5 | // The return type must always be annotated. 6 | fn sale_price(price: i64) -> i64 { 7 | if is_even(price) { 8 | price - 10 9 | } else { 10 | price - 3 11 | } 12 | } 13 | 14 | fn main() { 15 | let original_price = 51; 16 | println!("Your sale price is {}", sale_price(original_price)); 17 | } 18 | -------------------------------------------------------------------------------- /solutions/02_functions/functions5.rs: -------------------------------------------------------------------------------- 1 | fn square(num: i32) -> i32 { 2 | // Removed the semicolon `;` at the end of the line below to implicitly return the result. 3 | num * num 4 | } 5 | 6 | fn main() { 7 | let answer = square(3); 8 | println!("The square of 3 is {answer}"); 9 | } 10 | -------------------------------------------------------------------------------- /solutions/03_if/if1.rs: -------------------------------------------------------------------------------- 1 | fn bigger(a: i32, b: i32) -> i32 { 2 | if a > b { a } else { b } 3 | } 4 | 5 | fn main() { 6 | // You can optionally experiment here. 7 | } 8 | 9 | // Don't mind this for now :) 10 | #[cfg(test)] 11 | mod tests { 12 | use super::*; 13 | 14 | #[test] 15 | fn ten_is_bigger_than_eight() { 16 | assert_eq!(10, bigger(10, 8)); 17 | } 18 | 19 | #[test] 20 | fn fortytwo_is_bigger_than_thirtytwo() { 21 | assert_eq!(42, bigger(32, 42)); 22 | } 23 | 24 | #[test] 25 | fn equal_numbers() { 26 | assert_eq!(42, bigger(42, 42)); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /solutions/03_if/if2.rs: -------------------------------------------------------------------------------- 1 | fn picky_eater(food: &str) -> &str { 2 | if food == "strawberry" { 3 | "Yummy!" 4 | } else if food == "potato" { 5 | "I guess I can eat that." 6 | } else { 7 | "No thanks!" 8 | } 9 | } 10 | 11 | fn main() { 12 | // You can optionally experiment here. 13 | } 14 | 15 | #[cfg(test)] 16 | mod tests { 17 | use super::*; 18 | 19 | #[test] 20 | fn yummy_food() { 21 | assert_eq!(picky_eater("strawberry"), "Yummy!"); 22 | } 23 | 24 | #[test] 25 | fn neutral_food() { 26 | assert_eq!(picky_eater("potato"), "I guess I can eat that."); 27 | } 28 | 29 | #[test] 30 | fn default_disliked_food() { 31 | assert_eq!(picky_eater("broccoli"), "No thanks!"); 32 | assert_eq!(picky_eater("gummy bears"), "No thanks!"); 33 | assert_eq!(picky_eater("literally anything"), "No thanks!"); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /solutions/03_if/if3.rs: -------------------------------------------------------------------------------- 1 | fn animal_habitat(animal: &str) -> &str { 2 | let identifier = if animal == "crab" { 3 | 1 4 | } else if animal == "gopher" { 5 | 2 6 | } else if animal == "snake" { 7 | 3 8 | } else { 9 | // Any unused identifier. 10 | 4 11 | }; 12 | 13 | // Instead of such an identifier, you would use an enum in Rust. 14 | // But we didn't get into enums yet. 15 | if identifier == 1 { 16 | "Beach" 17 | } else if identifier == 2 { 18 | "Burrow" 19 | } else if identifier == 3 { 20 | "Desert" 21 | } else { 22 | "Unknown" 23 | } 24 | } 25 | 26 | fn main() { 27 | // You can optionally experiment here. 28 | } 29 | 30 | #[cfg(test)] 31 | mod tests { 32 | use super::*; 33 | 34 | #[test] 35 | fn gopher_lives_in_burrow() { 36 | assert_eq!(animal_habitat("gopher"), "Burrow") 37 | } 38 | 39 | #[test] 40 | fn snake_lives_in_desert() { 41 | assert_eq!(animal_habitat("snake"), "Desert") 42 | } 43 | 44 | #[test] 45 | fn crab_lives_on_beach() { 46 | assert_eq!(animal_habitat("crab"), "Beach") 47 | } 48 | 49 | #[test] 50 | fn unknown_animal() { 51 | assert_eq!(animal_habitat("dinosaur"), "Unknown") 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /solutions/04_primitive_types/primitive_types1.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | let is_morning = true; 3 | if is_morning { 4 | println!("Good morning!"); 5 | } 6 | 7 | let is_evening = !is_morning; 8 | if is_evening { 9 | println!("Good evening!"); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /solutions/04_primitive_types/primitive_types2.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | let my_first_initial = 'C'; 3 | if my_first_initial.is_alphabetic() { 4 | println!("Alphabetical!"); 5 | } else if my_first_initial.is_numeric() { 6 | println!("Numerical!"); 7 | } else { 8 | println!("Neither alphabetic nor numeric!"); 9 | } 10 | 11 | // Example with an emoji. 12 | let your_character = '🦀'; 13 | 14 | if your_character.is_alphabetic() { 15 | println!("Alphabetical!"); 16 | } else if your_character.is_numeric() { 17 | println!("Numerical!"); 18 | } else { 19 | println!("Neither alphabetic nor numeric!"); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /solutions/04_primitive_types/primitive_types3.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | // An array with 100 elements of the value 42. 3 | let a = [42; 100]; 4 | 5 | if a.len() >= 100 { 6 | println!("Wow, that's a big array!"); 7 | } else { 8 | println!("Meh, I eat arrays like that for breakfast."); 9 | panic!("Array not big enough, more elements needed"); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /solutions/04_primitive_types/primitive_types4.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | // You can optionally experiment here. 3 | } 4 | 5 | #[cfg(test)] 6 | mod tests { 7 | #[test] 8 | fn slice_out_of_array() { 9 | let a = [1, 2, 3, 4, 5]; 10 | // 0 1 2 3 4 <- indices 11 | // ------- 12 | // | 13 | // +--- slice 14 | 15 | // Note that the upper index 4 is excluded. 16 | let nice_slice = &a[1..4]; 17 | assert_eq!([2, 3, 4], nice_slice); 18 | 19 | // The upper index can be included by using the syntax `..=` (with `=` sign) 20 | let nice_slice = &a[1..=3]; 21 | assert_eq!([2, 3, 4], nice_slice); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /solutions/04_primitive_types/primitive_types5.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | let cat = ("Furry McFurson", 3.5); 3 | 4 | // Destructuring the tuple. 5 | let (name, age) = cat; 6 | 7 | println!("{name} is {age} years old"); 8 | } 9 | -------------------------------------------------------------------------------- /solutions/04_primitive_types/primitive_types6.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | // You can optionally experiment here. 3 | } 4 | 5 | #[cfg(test)] 6 | mod tests { 7 | #[test] 8 | fn indexing_tuple() { 9 | let numbers = (1, 2, 3); 10 | 11 | // Tuple indexing syntax. 12 | let second = numbers.1; 13 | 14 | assert_eq!(second, 2, "This is not the 2nd number in the tuple!"); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /solutions/05_vecs/vecs1.rs: -------------------------------------------------------------------------------- 1 | fn array_and_vec() -> ([i32; 4], Vec) { 2 | let a = [10, 20, 30, 40]; // Array 3 | 4 | // Used the `vec!` macro. 5 | let v = vec![10, 20, 30, 40]; 6 | 7 | (a, v) 8 | } 9 | 10 | fn main() { 11 | // You can optionally experiment here. 12 | } 13 | 14 | #[cfg(test)] 15 | mod tests { 16 | use super::*; 17 | 18 | #[test] 19 | fn test_array_and_vec_similarity() { 20 | let (a, v) = array_and_vec(); 21 | assert_eq!(a, *v); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /solutions/05_vecs/vecs2.rs: -------------------------------------------------------------------------------- 1 | fn vec_loop(input: &[i32]) -> Vec { 2 | let mut output = Vec::new(); 3 | 4 | for element in input { 5 | output.push(2 * element); 6 | } 7 | 8 | output 9 | } 10 | 11 | fn vec_map_example(input: &[i32]) -> Vec { 12 | // An example of collecting a vector after mapping. 13 | // We map each element of the `input` slice to its value plus 1. 14 | // If the input is `[1, 2, 3]`, the output is `[2, 3, 4]`. 15 | input.iter().map(|element| element + 1).collect() 16 | } 17 | 18 | fn vec_map(input: &[i32]) -> Vec { 19 | // We will dive deeper into iterators, but for now, this is all what you 20 | // had to do! 21 | // Advanced note: This method is more efficient because it automatically 22 | // preallocates enough capacity. This can be done manually in `vec_loop` 23 | // using `Vec::with_capacity(input.len())` instead of `Vec::new()`. 24 | input.iter().map(|element| 2 * element).collect() 25 | } 26 | 27 | fn main() { 28 | // You can optionally experiment here. 29 | } 30 | 31 | #[cfg(test)] 32 | mod tests { 33 | use super::*; 34 | 35 | #[test] 36 | fn test_vec_loop() { 37 | let input = [2, 4, 6, 8, 10]; 38 | let ans = vec_loop(&input); 39 | assert_eq!(ans, [4, 8, 12, 16, 20]); 40 | } 41 | 42 | #[test] 43 | fn test_vec_map_example() { 44 | let input = [1, 2, 3]; 45 | let ans = vec_map_example(&input); 46 | assert_eq!(ans, [2, 3, 4]); 47 | } 48 | 49 | #[test] 50 | fn test_vec_map() { 51 | let input = [2, 4, 6, 8, 10]; 52 | let ans = vec_map(&input); 53 | assert_eq!(ans, [4, 8, 12, 16, 20]); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /solutions/06_move_semantics/move_semantics1.rs: -------------------------------------------------------------------------------- 1 | fn fill_vec(vec: Vec) -> Vec { 2 | let mut vec = vec; 3 | // ^^^ added 4 | 5 | vec.push(88); 6 | 7 | vec 8 | } 9 | 10 | fn main() { 11 | // You can optionally experiment here. 12 | } 13 | 14 | #[cfg(test)] 15 | mod tests { 16 | use super::*; 17 | 18 | #[test] 19 | fn move_semantics1() { 20 | let vec0 = vec![22, 44, 66]; 21 | let vec1 = fill_vec(vec0); 22 | // `vec0` can't be accessed anymore because it is moved to `fill_vec`. 23 | assert_eq!(vec1, vec![22, 44, 66, 88]); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /solutions/06_move_semantics/move_semantics2.rs: -------------------------------------------------------------------------------- 1 | fn fill_vec(vec: Vec) -> Vec { 2 | let mut vec = vec; 3 | 4 | vec.push(88); 5 | 6 | vec 7 | } 8 | 9 | fn main() { 10 | // You can optionally experiment here. 11 | } 12 | 13 | #[cfg(test)] 14 | mod tests { 15 | use super::*; 16 | 17 | #[test] 18 | fn move_semantics2() { 19 | let vec0 = vec![22, 44, 66]; 20 | 21 | // Cloning `vec0` so that the clone is moved into `fill_vec`, not `vec0` 22 | // itself. 23 | let vec1 = fill_vec(vec0.clone()); 24 | 25 | assert_eq!(vec0, [22, 44, 66]); 26 | assert_eq!(vec1, [22, 44, 66, 88]); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /solutions/06_move_semantics/move_semantics3.rs: -------------------------------------------------------------------------------- 1 | fn fill_vec(mut vec: Vec) -> Vec { 2 | // ^^^ added 3 | vec.push(88); 4 | 5 | vec 6 | } 7 | 8 | fn main() { 9 | // You can optionally experiment here. 10 | } 11 | 12 | #[cfg(test)] 13 | mod tests { 14 | use super::*; 15 | 16 | #[test] 17 | fn move_semantics3() { 18 | let vec0 = vec![22, 44, 66]; 19 | let vec1 = fill_vec(vec0); 20 | assert_eq!(vec1, [22, 44, 66, 88]); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /solutions/06_move_semantics/move_semantics4.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | // You can optionally experiment here. 3 | } 4 | 5 | #[cfg(test)] 6 | mod tests { 7 | #[test] 8 | fn move_semantics4() { 9 | let mut x = Vec::new(); 10 | let y = &mut x; 11 | // `y` used here. 12 | y.push(42); 13 | // The mutable reference `y` is not used anymore, 14 | // therefore a new reference can be created. 15 | let z = &mut x; 16 | z.push(13); 17 | assert_eq!(x, [42, 13]); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /solutions/06_move_semantics/move_semantics5.rs: -------------------------------------------------------------------------------- 1 | #![allow(clippy::ptr_arg)] 2 | 3 | // Borrows instead of taking ownership. 4 | // It is recommended to use `&str` instead of `&String` here. But this is 5 | // enough for now because we didn't handle strings yet. 6 | fn get_char(data: &String) -> char { 7 | data.chars().last().unwrap() 8 | } 9 | 10 | // Takes ownership instead of borrowing. 11 | fn string_uppercase(mut data: String) { 12 | data = data.to_uppercase(); 13 | 14 | println!("{data}"); 15 | } 16 | 17 | fn main() { 18 | let data = "Rust is great!".to_string(); 19 | 20 | get_char(&data); 21 | 22 | string_uppercase(data); 23 | } 24 | -------------------------------------------------------------------------------- /solutions/07_structs/structs1.rs: -------------------------------------------------------------------------------- 1 | struct ColorRegularStruct { 2 | red: u8, 3 | green: u8, 4 | blue: u8, 5 | } 6 | 7 | struct ColorTupleStruct(u8, u8, u8); 8 | 9 | #[derive(Debug)] 10 | struct UnitStruct; 11 | 12 | fn main() { 13 | // You can optionally experiment here. 14 | } 15 | 16 | #[cfg(test)] 17 | mod tests { 18 | use super::*; 19 | 20 | #[test] 21 | fn regular_structs() { 22 | let green = ColorRegularStruct { 23 | red: 0, 24 | green: 255, 25 | blue: 0, 26 | }; 27 | 28 | assert_eq!(green.red, 0); 29 | assert_eq!(green.green, 255); 30 | assert_eq!(green.blue, 0); 31 | } 32 | 33 | #[test] 34 | fn tuple_structs() { 35 | let green = ColorTupleStruct(0, 255, 0); 36 | 37 | assert_eq!(green.0, 0); 38 | assert_eq!(green.1, 255); 39 | assert_eq!(green.2, 0); 40 | } 41 | 42 | #[test] 43 | fn unit_structs() { 44 | let unit_struct = UnitStruct; 45 | let message = format!("{unit_struct:?}s are fun!"); 46 | 47 | assert_eq!(message, "UnitStructs are fun!"); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /solutions/07_structs/structs2.rs: -------------------------------------------------------------------------------- 1 | #[derive(Debug)] 2 | struct Order { 3 | name: String, 4 | year: u32, 5 | made_by_phone: bool, 6 | made_by_mobile: bool, 7 | made_by_email: bool, 8 | item_number: u32, 9 | count: u32, 10 | } 11 | 12 | fn create_order_template() -> Order { 13 | Order { 14 | name: String::from("Bob"), 15 | year: 2019, 16 | made_by_phone: false, 17 | made_by_mobile: false, 18 | made_by_email: true, 19 | item_number: 123, 20 | count: 0, 21 | } 22 | } 23 | 24 | fn main() { 25 | // You can optionally experiment here. 26 | } 27 | 28 | #[cfg(test)] 29 | mod tests { 30 | use super::*; 31 | 32 | #[test] 33 | fn your_order() { 34 | let order_template = create_order_template(); 35 | 36 | let your_order = Order { 37 | name: String::from("Hacker in Rust"), 38 | count: 1, 39 | // Struct update syntax 40 | ..order_template 41 | }; 42 | 43 | assert_eq!(your_order.name, "Hacker in Rust"); 44 | assert_eq!(your_order.year, order_template.year); 45 | assert_eq!(your_order.made_by_phone, order_template.made_by_phone); 46 | assert_eq!(your_order.made_by_mobile, order_template.made_by_mobile); 47 | assert_eq!(your_order.made_by_email, order_template.made_by_email); 48 | assert_eq!(your_order.item_number, order_template.item_number); 49 | assert_eq!(your_order.count, 1); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /solutions/07_structs/structs3.rs: -------------------------------------------------------------------------------- 1 | #[derive(Debug)] 2 | struct Package { 3 | sender_country: String, 4 | recipient_country: String, 5 | weight_in_grams: u32, 6 | } 7 | 8 | impl Package { 9 | fn new(sender_country: String, recipient_country: String, weight_in_grams: u32) -> Self { 10 | if weight_in_grams < 10 { 11 | // This isn't how you should handle errors in Rust, but we will 12 | // learn about error handling later. 13 | panic!("Can't ship a package with weight below 10 grams"); 14 | } 15 | 16 | Self { 17 | sender_country, 18 | recipient_country, 19 | weight_in_grams, 20 | } 21 | } 22 | 23 | fn is_international(&self) -> bool { 24 | // ^^^^^^^ added 25 | self.sender_country != self.recipient_country 26 | } 27 | 28 | fn get_fees(&self, cents_per_gram: u32) -> u32 { 29 | // ^^^^^^ added 30 | self.weight_in_grams * cents_per_gram 31 | } 32 | } 33 | 34 | fn main() { 35 | // You can optionally experiment here. 36 | } 37 | 38 | #[cfg(test)] 39 | mod tests { 40 | use super::*; 41 | 42 | #[test] 43 | #[should_panic] 44 | fn fail_creating_weightless_package() { 45 | let sender_country = String::from("Spain"); 46 | let recipient_country = String::from("Austria"); 47 | 48 | Package::new(sender_country, recipient_country, 5); 49 | } 50 | 51 | #[test] 52 | fn create_international_package() { 53 | let sender_country = String::from("Spain"); 54 | let recipient_country = String::from("Russia"); 55 | 56 | let package = Package::new(sender_country, recipient_country, 1200); 57 | 58 | assert!(package.is_international()); 59 | } 60 | 61 | #[test] 62 | fn create_local_package() { 63 | let sender_country = String::from("Canada"); 64 | let recipient_country = sender_country.clone(); 65 | 66 | let package = Package::new(sender_country, recipient_country, 1200); 67 | 68 | assert!(!package.is_international()); 69 | } 70 | 71 | #[test] 72 | fn calculate_transport_fees() { 73 | let sender_country = String::from("Spain"); 74 | let recipient_country = String::from("Spain"); 75 | 76 | let cents_per_gram = 3; 77 | 78 | let package = Package::new(sender_country, recipient_country, 1500); 79 | 80 | assert_eq!(package.get_fees(cents_per_gram), 4500); 81 | assert_eq!(package.get_fees(cents_per_gram * 2), 9000); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /solutions/08_enums/enums1.rs: -------------------------------------------------------------------------------- 1 | #[derive(Debug)] 2 | enum Message { 3 | Resize, 4 | Move, 5 | Echo, 6 | ChangeColor, 7 | Quit, 8 | } 9 | 10 | fn main() { 11 | println!("{:?}", Message::Resize); 12 | println!("{:?}", Message::Move); 13 | println!("{:?}", Message::Echo); 14 | println!("{:?}", Message::ChangeColor); 15 | println!("{:?}", Message::Quit); 16 | } 17 | -------------------------------------------------------------------------------- /solutions/08_enums/enums2.rs: -------------------------------------------------------------------------------- 1 | #[derive(Debug)] 2 | struct Point { 3 | x: u64, 4 | y: u64, 5 | } 6 | 7 | #[derive(Debug)] 8 | enum Message { 9 | Resize { width: u64, height: u64 }, 10 | Move(Point), 11 | Echo(String), 12 | ChangeColor(u8, u8, u8), 13 | Quit, 14 | } 15 | 16 | impl Message { 17 | fn call(&self) { 18 | println!("{self:?}"); 19 | } 20 | } 21 | 22 | fn main() { 23 | let messages = [ 24 | Message::Resize { 25 | width: 10, 26 | height: 30, 27 | }, 28 | Message::Move(Point { x: 10, y: 15 }), 29 | Message::Echo(String::from("hello world")), 30 | Message::ChangeColor(200, 255, 255), 31 | Message::Quit, 32 | ]; 33 | 34 | for message in &messages { 35 | message.call(); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /solutions/08_enums/enums3.rs: -------------------------------------------------------------------------------- 1 | struct Point { 2 | x: u64, 3 | y: u64, 4 | } 5 | 6 | enum Message { 7 | Resize { width: u64, height: u64 }, 8 | Move(Point), 9 | Echo(String), 10 | ChangeColor(u8, u8, u8), 11 | Quit, 12 | } 13 | 14 | struct State { 15 | width: u64, 16 | height: u64, 17 | position: Point, 18 | message: String, 19 | color: (u8, u8, u8), 20 | quit: bool, 21 | } 22 | 23 | impl State { 24 | fn resize(&mut self, width: u64, height: u64) { 25 | self.width = width; 26 | self.height = height; 27 | } 28 | 29 | fn move_position(&mut self, point: Point) { 30 | self.position = point; 31 | } 32 | 33 | fn echo(&mut self, s: String) { 34 | self.message = s; 35 | } 36 | 37 | fn change_color(&mut self, red: u8, green: u8, blue: u8) { 38 | self.color = (red, green, blue); 39 | } 40 | 41 | fn quit(&mut self) { 42 | self.quit = true; 43 | } 44 | 45 | fn process(&mut self, message: Message) { 46 | match message { 47 | Message::Resize { width, height } => self.resize(width, height), 48 | Message::Move(point) => self.move_position(point), 49 | Message::Echo(string) => self.echo(string), 50 | Message::ChangeColor(red, green, blue) => self.change_color(red, green, blue), 51 | Message::Quit => self.quit(), 52 | } 53 | } 54 | } 55 | 56 | fn main() { 57 | // You can optionally experiment here. 58 | } 59 | 60 | #[cfg(test)] 61 | mod tests { 62 | use super::*; 63 | 64 | #[test] 65 | fn test_match_message_call() { 66 | let mut state = State { 67 | width: 0, 68 | height: 0, 69 | position: Point { x: 0, y: 0 }, 70 | message: String::from("hello world"), 71 | color: (0, 0, 0), 72 | quit: false, 73 | }; 74 | 75 | state.process(Message::Resize { 76 | width: 10, 77 | height: 30, 78 | }); 79 | state.process(Message::Move(Point { x: 10, y: 15 })); 80 | state.process(Message::Echo(String::from("Hello world!"))); 81 | state.process(Message::ChangeColor(255, 0, 255)); 82 | state.process(Message::Quit); 83 | 84 | assert_eq!(state.width, 10); 85 | assert_eq!(state.height, 30); 86 | assert_eq!(state.position.x, 10); 87 | assert_eq!(state.position.y, 15); 88 | assert_eq!(state.message, "Hello world!"); 89 | assert_eq!(state.color, (255, 0, 255)); 90 | assert!(state.quit); 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /solutions/09_strings/strings1.rs: -------------------------------------------------------------------------------- 1 | fn current_favorite_color() -> String { 2 | // Equivalent to `String::from("blue")` 3 | "blue".to_string() 4 | } 5 | 6 | fn main() { 7 | let answer = current_favorite_color(); 8 | println!("My current favorite color is {answer}"); 9 | } 10 | -------------------------------------------------------------------------------- /solutions/09_strings/strings2.rs: -------------------------------------------------------------------------------- 1 | fn is_a_color_word(attempt: &str) -> bool { 2 | attempt == "green" || attempt == "blue" || attempt == "red" 3 | } 4 | 5 | fn main() { 6 | let word = String::from("green"); 7 | 8 | if is_a_color_word(&word) { 9 | // ^ added to have `&String` which is automatically 10 | // coerced to `&str` by the compiler. 11 | println!("That is a color word I know!"); 12 | } else { 13 | println!("That is not a color word I know."); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /solutions/09_strings/strings3.rs: -------------------------------------------------------------------------------- 1 | fn trim_me(input: &str) -> &str { 2 | input.trim() 3 | } 4 | 5 | fn compose_me(input: &str) -> String { 6 | // The macro `format!` has the same syntax as `println!`, but it returns a 7 | // string instead of printing it to the terminal. 8 | // Equivalent to `input.to_string() + " world!"` 9 | format!("{input} world!") 10 | } 11 | 12 | fn replace_me(input: &str) -> String { 13 | input.replace("cars", "balloons") 14 | } 15 | 16 | fn main() { 17 | // You can optionally experiment here. 18 | } 19 | 20 | #[cfg(test)] 21 | mod tests { 22 | use super::*; 23 | 24 | #[test] 25 | fn trim_a_string() { 26 | assert_eq!(trim_me("Hello! "), "Hello!"); 27 | assert_eq!(trim_me(" What's up!"), "What's up!"); 28 | assert_eq!(trim_me(" Hola! "), "Hola!"); 29 | assert_eq!(trim_me("Hi!"), "Hi!"); 30 | } 31 | 32 | #[test] 33 | fn compose_a_string() { 34 | assert_eq!(compose_me("Hello"), "Hello world!"); 35 | assert_eq!(compose_me("Goodbye"), "Goodbye world!"); 36 | } 37 | 38 | #[test] 39 | fn replace_a_string() { 40 | assert_eq!( 41 | replace_me("I think cars are cool"), 42 | "I think balloons are cool", 43 | ); 44 | assert_eq!( 45 | replace_me("I love to look at cars"), 46 | "I love to look at balloons", 47 | ); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /solutions/09_strings/strings4.rs: -------------------------------------------------------------------------------- 1 | fn string_slice(arg: &str) { 2 | println!("{arg}"); 3 | } 4 | 5 | fn string(arg: String) { 6 | println!("{arg}"); 7 | } 8 | 9 | fn main() { 10 | string_slice("blue"); 11 | 12 | string("red".to_string()); 13 | 14 | string(String::from("hi")); 15 | 16 | string("rust is fun!".to_owned()); 17 | 18 | // Here, both answers work. 19 | // `.into()` converts a type into an expected type. 20 | // If it is called where `String` is expected, it will convert `&str` to `String`. 21 | string("nice weather".into()); 22 | // But if it is called where `&str` is expected, then `&str` is kept `&str` since no conversion is needed. 23 | // If you remove the `#[allow(…)]` line, then Clippy will tell you to remove `.into()` below since it is a useless conversion. 24 | #[allow(clippy::useless_conversion)] 25 | string_slice("nice weather".into()); 26 | 27 | string(format!("Interpolation {}", "Station")); 28 | 29 | // WARNING: This is byte indexing, not character indexing. 30 | // Character indexing can be done using `s.chars().nth(INDEX)`. 31 | string_slice(&String::from("abc")[0..1]); 32 | 33 | string_slice(" hello there ".trim()); 34 | 35 | string("Happy Monday!".replace("Mon", "Tues")); 36 | 37 | string("mY sHiFt KeY iS sTiCkY".to_lowercase()); 38 | } 39 | -------------------------------------------------------------------------------- /solutions/10_modules/modules1.rs: -------------------------------------------------------------------------------- 1 | mod sausage_factory { 2 | fn get_secret_recipe() -> String { 3 | String::from("Ginger") 4 | } 5 | 6 | // Added `pub` before `fn` to make the function accessible outside the module. 7 | pub fn make_sausage() { 8 | get_secret_recipe(); 9 | println!("sausage!"); 10 | } 11 | } 12 | 13 | fn main() { 14 | sausage_factory::make_sausage(); 15 | } 16 | -------------------------------------------------------------------------------- /solutions/10_modules/modules2.rs: -------------------------------------------------------------------------------- 1 | mod delicious_snacks { 2 | // Added `pub` and used the expected alias after `as`. 3 | pub use self::fruits::PEAR as fruit; 4 | pub use self::veggies::CUCUMBER as veggie; 5 | 6 | mod fruits { 7 | pub const PEAR: &str = "Pear"; 8 | pub const APPLE: &str = "Apple"; 9 | } 10 | 11 | mod veggies { 12 | pub const CUCUMBER: &str = "Cucumber"; 13 | pub const CARROT: &str = "Carrot"; 14 | } 15 | } 16 | 17 | fn main() { 18 | println!( 19 | "favorite snacks: {} and {}", 20 | delicious_snacks::fruit, 21 | delicious_snacks::veggie, 22 | ); 23 | } 24 | -------------------------------------------------------------------------------- /solutions/10_modules/modules3.rs: -------------------------------------------------------------------------------- 1 | use std::time::{SystemTime, UNIX_EPOCH}; 2 | 3 | fn main() { 4 | match SystemTime::now().duration_since(UNIX_EPOCH) { 5 | Ok(n) => println!("1970-01-01 00:00:00 UTC was {} seconds ago!", n.as_secs()), 6 | Err(_) => panic!("SystemTime before UNIX EPOCH!"), 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /solutions/11_hashmaps/hashmaps1.rs: -------------------------------------------------------------------------------- 1 | // A basket of fruits in the form of a hash map needs to be defined. The key 2 | // represents the name of the fruit and the value represents how many of that 3 | // particular fruit is in the basket. You have to put at least 3 different 4 | // types of fruits (e.g apple, banana, mango) in the basket and the total count 5 | // of all the fruits should be at least 5. 6 | 7 | use std::collections::HashMap; 8 | 9 | fn fruit_basket() -> HashMap { 10 | // Declare the hash map. 11 | let mut basket = HashMap::new(); 12 | 13 | // Two bananas are already given for you :) 14 | basket.insert(String::from("banana"), 2); 15 | 16 | // Put more fruits in your basket. 17 | basket.insert(String::from("apple"), 3); 18 | basket.insert(String::from("mango"), 1); 19 | 20 | basket 21 | } 22 | 23 | fn main() { 24 | // You can optionally experiment here. 25 | } 26 | 27 | #[cfg(test)] 28 | mod tests { 29 | use super::*; 30 | 31 | #[test] 32 | fn at_least_three_types_of_fruits() { 33 | let basket = fruit_basket(); 34 | assert!(basket.len() >= 3); 35 | } 36 | 37 | #[test] 38 | fn at_least_five_fruits() { 39 | let basket = fruit_basket(); 40 | assert!(basket.values().sum::() >= 5); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /solutions/12_options/options1.rs: -------------------------------------------------------------------------------- 1 | // This function returns how much icecream there is left in the fridge. 2 | // If it's before 22:00 (24-hour system), then 5 scoops are left. At 22:00, 3 | // someone eats it all, so no icecream is left (value 0). Return `None` if 4 | // `hour_of_day` is higher than 23. 5 | fn maybe_icecream(hour_of_day: u16) -> Option { 6 | match hour_of_day { 7 | 0..=21 => Some(5), 8 | 22..=23 => Some(0), 9 | _ => None, 10 | } 11 | } 12 | 13 | fn main() { 14 | // You can optionally experiment here. 15 | } 16 | 17 | #[cfg(test)] 18 | mod tests { 19 | use super::*; 20 | 21 | #[test] 22 | fn raw_value() { 23 | // Using `unwrap` is fine in a test. 24 | let icecreams = maybe_icecream(12).unwrap(); 25 | 26 | assert_eq!(icecreams, 5); 27 | } 28 | 29 | #[test] 30 | fn check_icecream() { 31 | assert_eq!(maybe_icecream(0), Some(5)); 32 | assert_eq!(maybe_icecream(9), Some(5)); 33 | assert_eq!(maybe_icecream(18), Some(5)); 34 | assert_eq!(maybe_icecream(22), Some(0)); 35 | assert_eq!(maybe_icecream(23), Some(0)); 36 | assert_eq!(maybe_icecream(24), None); 37 | assert_eq!(maybe_icecream(25), None); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /solutions/12_options/options2.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | // You can optionally experiment here. 3 | } 4 | 5 | #[cfg(test)] 6 | mod tests { 7 | #[test] 8 | fn simple_option() { 9 | let target = "rustlings"; 10 | let optional_target = Some(target); 11 | 12 | // if-let 13 | if let Some(word) = optional_target { 14 | assert_eq!(word, target); 15 | } 16 | } 17 | 18 | #[test] 19 | fn layered_option() { 20 | let range = 10; 21 | let mut optional_integers: Vec> = vec![None]; 22 | 23 | for i in 1..=range { 24 | optional_integers.push(Some(i)); 25 | } 26 | 27 | let mut cursor = range; 28 | 29 | // while-let with nested pattern matching 30 | while let Some(Some(integer)) = optional_integers.pop() { 31 | assert_eq!(integer, cursor); 32 | cursor -= 1; 33 | } 34 | 35 | assert_eq!(cursor, 0); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /solutions/12_options/options3.rs: -------------------------------------------------------------------------------- 1 | #[derive(Debug)] 2 | struct Point { 3 | x: i32, 4 | y: i32, 5 | } 6 | 7 | fn main() { 8 | let optional_point = Some(Point { x: 100, y: 200 }); 9 | 10 | // Solution 1: Matching over the `Option` (not `&Option`) but without moving 11 | // out of the `Some` variant. 12 | match optional_point { 13 | Some(ref p) => println!("Coordinates are {},{}", p.x, p.y), 14 | // ^^^ added 15 | _ => panic!("No match!"), 16 | } 17 | 18 | // Solution 2: Matching over a reference (`&Option`) by added `&` before 19 | // `optional_point`. 20 | match &optional_point { 21 | //^ added 22 | Some(p) => println!("Coordinates are {},{}", p.x, p.y), 23 | _ => panic!("No match!"), 24 | } 25 | 26 | println!("{optional_point:?}"); 27 | } 28 | -------------------------------------------------------------------------------- /solutions/13_error_handling/errors1.rs: -------------------------------------------------------------------------------- 1 | fn generate_nametag_text(name: String) -> Result { 2 | // ^^^^^^ ^^^^^^ 3 | if name.is_empty() { 4 | // `Err(String)` instead of `None`. 5 | Err("Empty names aren't allowed".to_string()) 6 | } else { 7 | // `Ok` instead of `Some`. 8 | Ok(format!("Hi! My name is {name}")) 9 | } 10 | } 11 | 12 | fn main() { 13 | // You can optionally experiment here. 14 | } 15 | 16 | #[cfg(test)] 17 | mod tests { 18 | use super::*; 19 | 20 | #[test] 21 | fn generates_nametag_text_for_a_nonempty_name() { 22 | assert_eq!( 23 | generate_nametag_text("Beyoncé".to_string()).as_deref(), 24 | Ok("Hi! My name is Beyoncé"), 25 | ); 26 | } 27 | 28 | #[test] 29 | fn explains_why_generating_nametag_text_fails() { 30 | assert_eq!( 31 | generate_nametag_text(String::new()) 32 | .as_ref() 33 | .map_err(|e| e.as_str()), 34 | Err("Empty names aren't allowed"), 35 | ); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /solutions/13_error_handling/errors2.rs: -------------------------------------------------------------------------------- 1 | // Say we're writing a game where you can buy items with tokens. All items cost 2 | // 5 tokens, and whenever you purchase items there is a processing fee of 1 3 | // token. A player of the game will type in how many items they want to buy, and 4 | // the `total_cost` function will calculate the total cost of the items. Since 5 | // the player typed in the quantity, we get it as a string. They might have 6 | // typed anything, not just numbers! 7 | // 8 | // Right now, this function isn't handling the error case at all. What we want 9 | // to do is: If we call the `total_cost` function on a string that is not a 10 | // number, that function will return a `ParseIntError`. In that case, we want to 11 | // immediately return that error from our function and not try to multiply and 12 | // add. 13 | // 14 | // There are at least two ways to implement this that are both correct. But one 15 | // is a lot shorter! 16 | 17 | use std::num::ParseIntError; 18 | 19 | #[allow(unused_variables, clippy::question_mark)] 20 | fn total_cost(item_quantity: &str) -> Result { 21 | let processing_fee = 1; 22 | let cost_per_item = 5; 23 | 24 | // Added `?` to propagate the error. 25 | let qty = item_quantity.parse::()?; 26 | // ^ added 27 | 28 | // Equivalent to this verbose version: 29 | let qty = match item_quantity.parse::() { 30 | Ok(v) => v, 31 | Err(e) => return Err(e), 32 | }; 33 | 34 | Ok(qty * cost_per_item + processing_fee) 35 | } 36 | 37 | fn main() { 38 | // You can optionally experiment here. 39 | } 40 | 41 | #[cfg(test)] 42 | mod tests { 43 | use super::*; 44 | use std::num::IntErrorKind; 45 | 46 | #[test] 47 | fn item_quantity_is_a_valid_number() { 48 | assert_eq!(total_cost("34"), Ok(171)); 49 | } 50 | 51 | #[test] 52 | fn item_quantity_is_an_invalid_number() { 53 | assert_eq!( 54 | total_cost("beep boop").unwrap_err().kind(), 55 | &IntErrorKind::InvalidDigit, 56 | ); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /solutions/13_error_handling/errors3.rs: -------------------------------------------------------------------------------- 1 | // This is a program that is trying to use a completed version of the 2 | // `total_cost` function from the previous exercise. It's not working though! 3 | // Why not? What should we do to fix it? 4 | 5 | use std::num::ParseIntError; 6 | 7 | // Don't change this function. 8 | fn total_cost(item_quantity: &str) -> Result { 9 | let processing_fee = 1; 10 | let cost_per_item = 5; 11 | let qty = item_quantity.parse::()?; 12 | 13 | Ok(qty * cost_per_item + processing_fee) 14 | } 15 | 16 | fn main() -> Result<(), ParseIntError> { 17 | // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ added 18 | let mut tokens = 100; 19 | let pretend_user_input = "8"; 20 | 21 | let cost = total_cost(pretend_user_input)?; 22 | 23 | if cost > tokens { 24 | println!("You can't afford that many!"); 25 | } else { 26 | tokens -= cost; 27 | println!("You now have {tokens} tokens."); 28 | } 29 | 30 | // Added this line to return the `Ok` variant of the expected `Result`. 31 | Ok(()) 32 | } 33 | -------------------------------------------------------------------------------- /solutions/13_error_handling/errors4.rs: -------------------------------------------------------------------------------- 1 | use std::cmp::Ordering; 2 | 3 | #[derive(PartialEq, Debug)] 4 | enum CreationError { 5 | Negative, 6 | Zero, 7 | } 8 | 9 | #[derive(PartialEq, Debug)] 10 | struct PositiveNonzeroInteger(u64); 11 | 12 | impl PositiveNonzeroInteger { 13 | fn new(value: i64) -> Result { 14 | match value.cmp(&0) { 15 | Ordering::Less => Err(CreationError::Negative), 16 | Ordering::Equal => Err(CreationError::Zero), 17 | Ordering::Greater => Ok(Self(value as u64)), 18 | } 19 | } 20 | } 21 | 22 | fn main() { 23 | // You can optionally experiment here. 24 | } 25 | 26 | #[cfg(test)] 27 | mod tests { 28 | use super::*; 29 | 30 | #[test] 31 | fn test_creation() { 32 | assert_eq!( 33 | PositiveNonzeroInteger::new(10), 34 | Ok(PositiveNonzeroInteger(10)), 35 | ); 36 | assert_eq!( 37 | PositiveNonzeroInteger::new(-10), 38 | Err(CreationError::Negative), 39 | ); 40 | assert_eq!(PositiveNonzeroInteger::new(0), Err(CreationError::Zero)); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /solutions/13_error_handling/errors5.rs: -------------------------------------------------------------------------------- 1 | // This exercise is an altered version of the `errors4` exercise. It uses some 2 | // concepts that we won't get to until later in the course, like `Box` and the 3 | // `From` trait. It's not important to understand them in detail right now, but 4 | // you can read ahead if you like. For now, think of the `Box` type as 5 | // an "I want anything that does ???" type. 6 | // 7 | // In short, this particular use case for boxes is for when you want to own a 8 | // value and you care only that it is a type which implements a particular 9 | // trait. To do so, The `Box` is declared as of type `Box` where 10 | // `Trait` is the trait the compiler looks for on any value used in that 11 | // context. For this exercise, that context is the potential errors which 12 | // can be returned in a `Result`. 13 | 14 | use std::error::Error; 15 | use std::fmt; 16 | 17 | #[derive(PartialEq, Debug)] 18 | enum CreationError { 19 | Negative, 20 | Zero, 21 | } 22 | 23 | // This is required so that `CreationError` can implement `Error`. 24 | impl fmt::Display for CreationError { 25 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 26 | let description = match *self { 27 | CreationError::Negative => "number is negative", 28 | CreationError::Zero => "number is zero", 29 | }; 30 | f.write_str(description) 31 | } 32 | } 33 | 34 | impl Error for CreationError {} 35 | 36 | #[derive(PartialEq, Debug)] 37 | struct PositiveNonzeroInteger(u64); 38 | 39 | impl PositiveNonzeroInteger { 40 | fn new(value: i64) -> Result { 41 | match value { 42 | x if x < 0 => Err(CreationError::Negative), 43 | 0 => Err(CreationError::Zero), 44 | x => Ok(PositiveNonzeroInteger(x as u64)), 45 | } 46 | } 47 | } 48 | 49 | fn main() -> Result<(), Box> { 50 | let pretend_user_input = "42"; 51 | let x: i64 = pretend_user_input.parse()?; 52 | println!("output={:?}", PositiveNonzeroInteger::new(x)?); 53 | Ok(()) 54 | } 55 | -------------------------------------------------------------------------------- /solutions/14_generics/generics1.rs: -------------------------------------------------------------------------------- 1 | // `Vec` is generic over the type `T`. In most cases, the compiler is able to 2 | // infer `T`, for example after pushing a value with a concrete type to the vector. 3 | // But in this exercise, the compiler needs some help through a type annotation. 4 | 5 | fn main() { 6 | // `u8` and `i8` can both be converted to `i16`. 7 | let mut numbers: Vec = Vec::new(); 8 | // ^^^^^^^^^^ added 9 | 10 | // Don't change the lines below. 11 | let n1: u8 = 42; 12 | numbers.push(n1.into()); 13 | let n2: i8 = -1; 14 | numbers.push(n2.into()); 15 | 16 | println!("{numbers:?}"); 17 | } 18 | -------------------------------------------------------------------------------- /solutions/14_generics/generics2.rs: -------------------------------------------------------------------------------- 1 | struct Wrapper { 2 | value: T, 3 | } 4 | 5 | impl Wrapper { 6 | fn new(value: T) -> Self { 7 | Wrapper { value } 8 | } 9 | } 10 | 11 | fn main() { 12 | // You can optionally experiment here. 13 | } 14 | 15 | #[cfg(test)] 16 | mod tests { 17 | use super::*; 18 | 19 | #[test] 20 | fn store_u32_in_wrapper() { 21 | assert_eq!(Wrapper::new(42).value, 42); 22 | } 23 | 24 | #[test] 25 | fn store_str_in_wrapper() { 26 | assert_eq!(Wrapper::new("Foo").value, "Foo"); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /solutions/15_traits/traits1.rs: -------------------------------------------------------------------------------- 1 | // The trait `AppendBar` has only one function which appends "Bar" to any object 2 | // implementing this trait. 3 | trait AppendBar { 4 | fn append_bar(self) -> Self; 5 | } 6 | 7 | impl AppendBar for String { 8 | fn append_bar(self) -> Self { 9 | self + "Bar" 10 | } 11 | } 12 | 13 | fn main() { 14 | let s = String::from("Foo"); 15 | let s = s.append_bar(); 16 | println!("s: {s}"); 17 | } 18 | 19 | #[cfg(test)] 20 | mod tests { 21 | use super::*; 22 | 23 | #[test] 24 | fn is_foo_bar() { 25 | assert_eq!(String::from("Foo").append_bar(), "FooBar"); 26 | } 27 | 28 | #[test] 29 | fn is_bar_bar() { 30 | assert_eq!(String::from("").append_bar().append_bar(), "BarBar"); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /solutions/15_traits/traits2.rs: -------------------------------------------------------------------------------- 1 | trait AppendBar { 2 | fn append_bar(self) -> Self; 3 | } 4 | 5 | impl AppendBar for Vec { 6 | fn append_bar(mut self) -> Self { 7 | // ^^^ this is important 8 | self.push(String::from("Bar")); 9 | self 10 | } 11 | } 12 | 13 | fn main() { 14 | // You can optionally experiment here. 15 | } 16 | 17 | #[cfg(test)] 18 | mod tests { 19 | use super::*; 20 | 21 | #[test] 22 | fn is_vec_pop_eq_bar() { 23 | let mut foo = vec![String::from("Foo")].append_bar(); 24 | assert_eq!(foo.pop().unwrap(), "Bar"); 25 | assert_eq!(foo.pop().unwrap(), "Foo"); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /solutions/15_traits/traits3.rs: -------------------------------------------------------------------------------- 1 | trait Licensed { 2 | fn licensing_info(&self) -> String { 3 | "Default license".to_string() 4 | } 5 | } 6 | 7 | struct SomeSoftware { 8 | version_number: i32, 9 | } 10 | 11 | struct OtherSoftware { 12 | version_number: String, 13 | } 14 | 15 | impl Licensed for SomeSoftware {} 16 | impl Licensed for OtherSoftware {} 17 | 18 | fn main() { 19 | // You can optionally experiment here. 20 | } 21 | 22 | #[cfg(test)] 23 | mod tests { 24 | use super::*; 25 | 26 | #[test] 27 | fn is_licensing_info_the_same() { 28 | let licensing_info = "Default license"; 29 | let some_software = SomeSoftware { version_number: 1 }; 30 | let other_software = OtherSoftware { 31 | version_number: "v2.0.0".to_string(), 32 | }; 33 | assert_eq!(some_software.licensing_info(), licensing_info); 34 | assert_eq!(other_software.licensing_info(), licensing_info); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /solutions/15_traits/traits4.rs: -------------------------------------------------------------------------------- 1 | trait Licensed { 2 | fn licensing_info(&self) -> String { 3 | "Default license".to_string() 4 | } 5 | } 6 | 7 | struct SomeSoftware; 8 | struct OtherSoftware; 9 | 10 | impl Licensed for SomeSoftware {} 11 | impl Licensed for OtherSoftware {} 12 | 13 | fn compare_license_types(software1: impl Licensed, software2: impl Licensed) -> bool { 14 | // ^^^^^^^^^^^^^ ^^^^^^^^^^^^^ 15 | software1.licensing_info() == software2.licensing_info() 16 | } 17 | 18 | fn main() { 19 | // You can optionally experiment here. 20 | } 21 | 22 | #[cfg(test)] 23 | mod tests { 24 | use super::*; 25 | 26 | #[test] 27 | fn compare_license_information() { 28 | assert!(compare_license_types(SomeSoftware, OtherSoftware)); 29 | } 30 | 31 | #[test] 32 | fn compare_license_information_backwards() { 33 | assert!(compare_license_types(OtherSoftware, SomeSoftware)); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /solutions/15_traits/traits5.rs: -------------------------------------------------------------------------------- 1 | trait SomeTrait { 2 | fn some_function(&self) -> bool { 3 | true 4 | } 5 | } 6 | 7 | trait OtherTrait { 8 | fn other_function(&self) -> bool { 9 | true 10 | } 11 | } 12 | 13 | struct SomeStruct; 14 | impl SomeTrait for SomeStruct {} 15 | impl OtherTrait for SomeStruct {} 16 | 17 | struct OtherStruct; 18 | impl SomeTrait for OtherStruct {} 19 | impl OtherTrait for OtherStruct {} 20 | 21 | fn some_func(item: impl SomeTrait + OtherTrait) -> bool { 22 | // ^^^^^^^^^^^^^^^^^^^^^^^^^^^ 23 | item.some_function() && item.other_function() 24 | } 25 | 26 | fn main() { 27 | // You can optionally experiment here. 28 | } 29 | 30 | #[cfg(test)] 31 | mod tests { 32 | use super::*; 33 | 34 | #[test] 35 | fn test_some_func() { 36 | assert!(some_func(SomeStruct)); 37 | assert!(some_func(OtherStruct)); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /solutions/16_lifetimes/lifetimes1.rs: -------------------------------------------------------------------------------- 1 | // The Rust compiler needs to know how to check whether supplied references are 2 | // valid, so that it can let the programmer know if a reference is at risk of 3 | // going out of scope before it is used. Remember, references are borrows and do 4 | // not own their own data. What if their owner goes out of scope? 5 | 6 | fn longest<'a>(x: &'a str, y: &'a str) -> &'a str { 7 | // ^^^^ ^^ ^^ ^^ 8 | if x.len() > y.len() { x } else { y } 9 | } 10 | 11 | fn main() { 12 | // You can optionally experiment here. 13 | } 14 | 15 | #[cfg(test)] 16 | mod tests { 17 | use super::*; 18 | 19 | #[test] 20 | fn test_longest() { 21 | assert_eq!(longest("abcd", "123"), "abcd"); 22 | assert_eq!(longest("abc", "1234"), "1234"); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /solutions/16_lifetimes/lifetimes2.rs: -------------------------------------------------------------------------------- 1 | fn longest<'a>(x: &'a str, y: &'a str) -> &'a str { 2 | if x.len() > y.len() { x } else { y } 3 | } 4 | 5 | fn main() { 6 | let string1 = String::from("long string is long"); 7 | // Solution1: You can move `strings2` out of the inner block so that it is 8 | // not dropped before the print statement. 9 | let string2 = String::from("xyz"); 10 | let result; 11 | { 12 | result = longest(&string1, &string2); 13 | } 14 | println!("The longest string is '{result}'"); 15 | // `string2` dropped at the end of the function. 16 | 17 | // ========================================================================= 18 | 19 | let string1 = String::from("long string is long"); 20 | let result; 21 | { 22 | let string2 = String::from("xyz"); 23 | result = longest(&string1, &string2); 24 | // Solution2: You can move the print statement into the inner block so 25 | // that it is executed before `string2` is dropped. 26 | println!("The longest string is '{result}'"); 27 | // `string2` dropped here (end of the inner scope). 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /solutions/16_lifetimes/lifetimes3.rs: -------------------------------------------------------------------------------- 1 | // Lifetimes are also needed when structs hold references. 2 | 3 | struct Book<'a> { 4 | // ^^^^ added a lifetime annotation 5 | author: &'a str, 6 | // ^^ 7 | title: &'a str, 8 | // ^^ 9 | } 10 | 11 | fn main() { 12 | let book = Book { 13 | author: "George Orwell", 14 | title: "1984", 15 | }; 16 | 17 | println!("{} by {}", book.title, book.author); 18 | } 19 | -------------------------------------------------------------------------------- /solutions/17_tests/tests1.rs: -------------------------------------------------------------------------------- 1 | // Tests are important to ensure that your code does what you think it should 2 | // do. 3 | 4 | fn is_even(n: i64) -> bool { 5 | n % 2 == 0 6 | } 7 | 8 | fn main() { 9 | // You can optionally experiment here. 10 | } 11 | 12 | #[cfg(test)] 13 | mod tests { 14 | // When writing unit tests, it is common to import everything from the outer 15 | // module (`super`) using a wildcard. 16 | use super::*; 17 | 18 | #[test] 19 | fn you_can_assert() { 20 | assert!(is_even(0)); 21 | assert!(!is_even(-1)); 22 | // ^ You can assert `false` using the negation operator `!`. 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /solutions/17_tests/tests2.rs: -------------------------------------------------------------------------------- 1 | // Calculates the power of 2 using a bit shift. 2 | // `1 << n` is equivalent to "2 to the power of n". 3 | fn power_of_2(n: u8) -> u64 { 4 | 1 << n 5 | } 6 | 7 | fn main() { 8 | // You can optionally experiment here. 9 | } 10 | 11 | #[cfg(test)] 12 | mod tests { 13 | use super::*; 14 | 15 | #[test] 16 | fn you_can_assert_eq() { 17 | assert_eq!(power_of_2(0), 1); 18 | assert_eq!(power_of_2(1), 2); 19 | assert_eq!(power_of_2(2), 4); 20 | assert_eq!(power_of_2(3), 8); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /solutions/17_tests/tests3.rs: -------------------------------------------------------------------------------- 1 | struct Rectangle { 2 | width: i32, 3 | height: i32, 4 | } 5 | 6 | impl Rectangle { 7 | // Don't change this function. 8 | fn new(width: i32, height: i32) -> Self { 9 | if width <= 0 || height <= 0 { 10 | // Returning a `Result` would be better here. But we want to learn 11 | // how to test functions that can panic. 12 | panic!("Rectangle width and height must be positive"); 13 | } 14 | 15 | Rectangle { width, height } 16 | } 17 | } 18 | 19 | fn main() { 20 | // You can optionally experiment here. 21 | } 22 | 23 | #[cfg(test)] 24 | mod tests { 25 | use super::*; 26 | 27 | #[test] 28 | fn correct_width_and_height() { 29 | let rect = Rectangle::new(10, 20); 30 | assert_eq!(rect.width, 10); // Check width 31 | assert_eq!(rect.height, 20); // Check height 32 | } 33 | 34 | #[test] 35 | #[should_panic] // Added this attribute to check that the test panics. 36 | fn negative_width() { 37 | let _rect = Rectangle::new(-10, 10); 38 | } 39 | 40 | #[test] 41 | #[should_panic] // Added this attribute to check that the test panics. 42 | fn negative_height() { 43 | let _rect = Rectangle::new(10, -10); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /solutions/18_iterators/iterators1.rs: -------------------------------------------------------------------------------- 1 | // When performing operations on elements within a collection, iterators are 2 | // essential. This module helps you get familiar with the structure of using an 3 | // iterator and how to go through elements within an iterable collection. 4 | 5 | fn main() { 6 | // You can optionally experiment here. 7 | } 8 | 9 | #[cfg(test)] 10 | mod tests { 11 | #[test] 12 | fn iterators() { 13 | let my_fav_fruits = ["banana", "custard apple", "avocado", "peach", "raspberry"]; 14 | 15 | // Create an iterator over the array. 16 | let mut fav_fruits_iterator = my_fav_fruits.iter(); 17 | 18 | assert_eq!(fav_fruits_iterator.next(), Some(&"banana")); 19 | assert_eq!(fav_fruits_iterator.next(), Some(&"custard apple")); 20 | assert_eq!(fav_fruits_iterator.next(), Some(&"avocado")); 21 | assert_eq!(fav_fruits_iterator.next(), Some(&"peach")); 22 | assert_eq!(fav_fruits_iterator.next(), Some(&"raspberry")); 23 | assert_eq!(fav_fruits_iterator.next(), None); 24 | // ^^^^ reached the end 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /solutions/18_iterators/iterators2.rs: -------------------------------------------------------------------------------- 1 | // In this exercise, you'll learn some of the unique advantages that iterators 2 | // can offer. 3 | 4 | // "hello" -> "Hello" 5 | fn capitalize_first(input: &str) -> String { 6 | let mut chars = input.chars(); 7 | match chars.next() { 8 | None => String::new(), 9 | Some(first) => first.to_uppercase().to_string() + chars.as_str(), 10 | } 11 | } 12 | 13 | // Apply the `capitalize_first` function to a slice of string slices. 14 | // Return a vector of strings. 15 | // ["hello", "world"] -> ["Hello", "World"] 16 | fn capitalize_words_vector(words: &[&str]) -> Vec { 17 | words.iter().map(|word| capitalize_first(word)).collect() 18 | } 19 | 20 | // Apply the `capitalize_first` function again to a slice of string 21 | // slices. Return a single string. 22 | // ["hello", " ", "world"] -> "Hello World" 23 | fn capitalize_words_string(words: &[&str]) -> String { 24 | words.iter().map(|word| capitalize_first(word)).collect() 25 | } 26 | 27 | fn main() { 28 | // You can optionally experiment here. 29 | } 30 | 31 | #[cfg(test)] 32 | mod tests { 33 | use super::*; 34 | 35 | #[test] 36 | fn test_success() { 37 | assert_eq!(capitalize_first("hello"), "Hello"); 38 | } 39 | 40 | #[test] 41 | fn test_empty() { 42 | assert_eq!(capitalize_first(""), ""); 43 | } 44 | 45 | #[test] 46 | fn test_iterate_string_vec() { 47 | let words = vec!["hello", "world"]; 48 | assert_eq!(capitalize_words_vector(&words), ["Hello", "World"]); 49 | } 50 | 51 | #[test] 52 | fn test_iterate_into_string() { 53 | let words = vec!["hello", " ", "world"]; 54 | assert_eq!(capitalize_words_string(&words), "Hello World"); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /solutions/18_iterators/iterators3.rs: -------------------------------------------------------------------------------- 1 | #[derive(Debug, PartialEq, Eq)] 2 | enum DivisionError { 3 | // Example: 42 / 0 4 | DivideByZero, 5 | // Only case for `i64`: `i64::MIN / -1` because the result is `i64::MAX + 1` 6 | IntegerOverflow, 7 | // Example: 5 / 2 = 2.5 8 | NotDivisible, 9 | } 10 | 11 | fn divide(a: i64, b: i64) -> Result { 12 | if b == 0 { 13 | return Err(DivisionError::DivideByZero); 14 | } 15 | 16 | if a == i64::MIN && b == -1 { 17 | return Err(DivisionError::IntegerOverflow); 18 | } 19 | 20 | if a % b != 0 { 21 | return Err(DivisionError::NotDivisible); 22 | } 23 | 24 | Ok(a / b) 25 | } 26 | 27 | fn result_with_list() -> Result, DivisionError> { 28 | // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 29 | let numbers = [27, 297, 38502, 81]; 30 | let division_results = numbers.into_iter().map(|n| divide(n, 27)); 31 | // Collects to the expected return type. Returns the first error in the 32 | // division results (if one exists). 33 | division_results.collect() 34 | } 35 | 36 | fn list_of_results() -> Vec> { 37 | // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 38 | let numbers = [27, 297, 38502, 81]; 39 | let division_results = numbers.into_iter().map(|n| divide(n, 27)); 40 | // Collects to the expected return type. 41 | division_results.collect() 42 | } 43 | 44 | fn main() { 45 | // You can optionally experiment here. 46 | } 47 | 48 | #[cfg(test)] 49 | mod tests { 50 | use super::*; 51 | 52 | #[test] 53 | fn test_success() { 54 | assert_eq!(divide(81, 9), Ok(9)); 55 | } 56 | 57 | #[test] 58 | fn test_divide_by_0() { 59 | assert_eq!(divide(81, 0), Err(DivisionError::DivideByZero)); 60 | } 61 | 62 | #[test] 63 | fn test_integer_overflow() { 64 | assert_eq!(divide(i64::MIN, -1), Err(DivisionError::IntegerOverflow)); 65 | } 66 | 67 | #[test] 68 | fn test_not_divisible() { 69 | assert_eq!(divide(81, 6), Err(DivisionError::NotDivisible)); 70 | } 71 | 72 | #[test] 73 | fn test_divide_0_by_something() { 74 | assert_eq!(divide(0, 81), Ok(0)); 75 | } 76 | 77 | #[test] 78 | fn test_result_with_list() { 79 | assert_eq!(result_with_list().unwrap(), [1, 11, 1426, 3]); 80 | } 81 | 82 | #[test] 83 | fn test_list_of_results() { 84 | assert_eq!(list_of_results(), [Ok(1), Ok(11), Ok(1426), Ok(3)]); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /solutions/18_iterators/iterators4.rs: -------------------------------------------------------------------------------- 1 | // 3 possible solutions are presented. 2 | 3 | // With `for` loop and a mutable variable. 4 | fn factorial_for(num: u64) -> u64 { 5 | let mut result = 1; 6 | 7 | for x in 2..=num { 8 | result *= x; 9 | } 10 | 11 | result 12 | } 13 | 14 | // Equivalent to `factorial_for` but shorter and without a `for` loop and 15 | // mutable variables. 16 | fn factorial_fold(num: u64) -> u64 { 17 | // Case num==0: The iterator 2..=0 is empty 18 | // -> The initial value of `fold` is returned which is 1. 19 | // Case num==1: The iterator 2..=1 is also empty 20 | // -> The initial value 1 is returned. 21 | // Case num==2: The iterator 2..=2 contains one element 22 | // -> The initial value 1 is multiplied by 2 and the result 23 | // is returned. 24 | // Case num==3: The iterator 2..=3 contains 2 elements 25 | // -> 1 * 2 is calculated, then the result 2 is multiplied by 26 | // the second element 3 so the result 6 is returned. 27 | // And so on… 28 | #[allow(clippy::unnecessary_fold)] 29 | (2..=num).fold(1, |acc, x| acc * x) 30 | } 31 | 32 | // Equivalent to `factorial_fold` but with a built-in method that is suggested 33 | // by Clippy. 34 | fn factorial_product(num: u64) -> u64 { 35 | (2..=num).product() 36 | } 37 | 38 | fn main() { 39 | // You can optionally experiment here. 40 | } 41 | 42 | #[cfg(test)] 43 | mod tests { 44 | use super::*; 45 | 46 | #[test] 47 | fn factorial_of_0() { 48 | assert_eq!(factorial_for(0), 1); 49 | assert_eq!(factorial_fold(0), 1); 50 | assert_eq!(factorial_product(0), 1); 51 | } 52 | 53 | #[test] 54 | fn factorial_of_1() { 55 | assert_eq!(factorial_for(1), 1); 56 | assert_eq!(factorial_fold(1), 1); 57 | assert_eq!(factorial_product(1), 1); 58 | } 59 | #[test] 60 | fn factorial_of_2() { 61 | assert_eq!(factorial_for(2), 2); 62 | assert_eq!(factorial_fold(2), 2); 63 | assert_eq!(factorial_product(2), 2); 64 | } 65 | 66 | #[test] 67 | fn factorial_of_4() { 68 | assert_eq!(factorial_for(4), 24); 69 | assert_eq!(factorial_fold(4), 24); 70 | assert_eq!(factorial_product(4), 24); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /solutions/19_smart_pointers/arc1.rs: -------------------------------------------------------------------------------- 1 | // In this exercise, we are given a `Vec` of `u32` called `numbers` with values 2 | // ranging from 0 to 99. We would like to use this set of numbers within 8 3 | // different threads simultaneously. Each thread is going to get the sum of 4 | // every eighth value with an offset. 5 | // 6 | // The first thread (offset 0), will sum 0, 8, 16, … 7 | // The second thread (offset 1), will sum 1, 9, 17, … 8 | // The third thread (offset 2), will sum 2, 10, 18, … 9 | // … 10 | // The eighth thread (offset 7), will sum 7, 15, 23, … 11 | // 12 | // Each thread should own a reference-counting pointer to the vector of 13 | // numbers. But `Rc` isn't thread-safe. Therefore, we need to use `Arc`. 14 | // 15 | // Don't get distracted by how threads are spawned and joined. We will practice 16 | // that later in the exercises about threads. 17 | 18 | // Don't change the lines below. 19 | #![forbid(unused_imports)] 20 | use std::{sync::Arc, thread}; 21 | 22 | fn main() { 23 | let numbers: Vec<_> = (0..100u32).collect(); 24 | 25 | let shared_numbers = Arc::new(numbers); 26 | // ^^^^^^^^^^^^^^^^^ 27 | 28 | let mut join_handles = Vec::new(); 29 | 30 | for offset in 0..8 { 31 | let child_numbers = Arc::clone(&shared_numbers); 32 | // ^^^^^^^^^^^^^^^^^^^^^^^^^^^ 33 | 34 | let handle = thread::spawn(move || { 35 | let sum: u32 = child_numbers.iter().filter(|&&n| n % 8 == offset).sum(); 36 | println!("Sum of offset {offset} is {sum}"); 37 | }); 38 | 39 | join_handles.push(handle); 40 | } 41 | 42 | for handle in join_handles.into_iter() { 43 | handle.join().unwrap(); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /solutions/19_smart_pointers/box1.rs: -------------------------------------------------------------------------------- 1 | // At compile time, Rust needs to know how much space a type takes up. This 2 | // becomes problematic for recursive types, where a value can have as part of 3 | // itself another value of the same type. To get around the issue, we can use a 4 | // `Box` - a smart pointer used to store data on the heap, which also allows us 5 | // to wrap a recursive type. 6 | // 7 | // The recursive type we're implementing in this exercise is the "cons list", a 8 | // data structure frequently found in functional programming languages. Each 9 | // item in a cons list contains two elements: The value of the current item and 10 | // the next item. The last item is a value called `Nil`. 11 | 12 | #[derive(PartialEq, Debug)] 13 | enum List { 14 | Cons(i32, Box), 15 | Nil, 16 | } 17 | 18 | fn create_empty_list() -> List { 19 | List::Nil 20 | } 21 | 22 | fn create_non_empty_list() -> List { 23 | List::Cons(42, Box::new(List::Nil)) 24 | } 25 | 26 | fn main() { 27 | println!("This is an empty cons list: {:?}", create_empty_list()); 28 | println!( 29 | "This is a non-empty cons list: {:?}", 30 | create_non_empty_list(), 31 | ); 32 | } 33 | 34 | #[cfg(test)] 35 | mod tests { 36 | use super::*; 37 | 38 | #[test] 39 | fn test_create_empty_list() { 40 | assert_eq!(create_empty_list(), List::Nil); 41 | } 42 | 43 | #[test] 44 | fn test_create_non_empty_list() { 45 | assert_ne!(create_empty_list(), create_non_empty_list()); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /solutions/19_smart_pointers/cow1.rs: -------------------------------------------------------------------------------- 1 | // This exercise explores the `Cow` (Clone-On-Write) smart pointer. It can 2 | // enclose and provide immutable access to borrowed data and clone the data 3 | // lazily when mutation or ownership is required. The type is designed to work 4 | // with general borrowed data via the `Borrow` trait. 5 | 6 | use std::borrow::Cow; 7 | 8 | fn abs_all(input: &mut Cow<[i32]>) { 9 | for ind in 0..input.len() { 10 | let value = input[ind]; 11 | if value < 0 { 12 | // Clones into a vector if not already owned. 13 | input.to_mut()[ind] = -value; 14 | } 15 | } 16 | } 17 | 18 | fn main() { 19 | // You can optionally experiment here. 20 | } 21 | 22 | #[cfg(test)] 23 | mod tests { 24 | use super::*; 25 | 26 | #[test] 27 | fn reference_mutation() { 28 | // Clone occurs because `input` needs to be mutated. 29 | let vec = vec![-1, 0, 1]; 30 | let mut input = Cow::from(&vec); 31 | abs_all(&mut input); 32 | assert!(matches!(input, Cow::Owned(_))); 33 | } 34 | 35 | #[test] 36 | fn reference_no_mutation() { 37 | // No clone occurs because `input` doesn't need to be mutated. 38 | let vec = vec![0, 1, 2]; 39 | let mut input = Cow::from(&vec); 40 | abs_all(&mut input); 41 | assert!(matches!(input, Cow::Borrowed(_))); 42 | // ^^^^^^^^^^^^^^^^ 43 | } 44 | 45 | #[test] 46 | fn owned_no_mutation() { 47 | // We can also pass `vec` without `&` so `Cow` owns it directly. In this 48 | // case, no mutation occurs (all numbers are already absolute) and thus 49 | // also no clone. But the result is still owned because it was never 50 | // borrowed or mutated. 51 | let vec = vec![0, 1, 2]; 52 | let mut input = Cow::from(vec); 53 | abs_all(&mut input); 54 | assert!(matches!(input, Cow::Owned(_))); 55 | // ^^^^^^^^^^^^^ 56 | } 57 | 58 | #[test] 59 | fn owned_mutation() { 60 | // Of course this is also the case if a mutation does occur (not all 61 | // numbers are absolute). In this case, the call to `to_mut()` in the 62 | // `abs_all` function returns a reference to the same data as before. 63 | let vec = vec![-1, 0, 1]; 64 | let mut input = Cow::from(vec); 65 | abs_all(&mut input); 66 | assert!(matches!(input, Cow::Owned(_))); 67 | // ^^^^^^^^^^^^^ 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /solutions/20_threads/threads1.rs: -------------------------------------------------------------------------------- 1 | // This program spawns multiple threads that each runs for at least 250ms, and 2 | // each thread returns how much time it took to complete. The program should 3 | // wait until all the spawned threads have finished and should collect their 4 | // return values into a vector. 5 | 6 | use std::{ 7 | thread, 8 | time::{Duration, Instant}, 9 | }; 10 | 11 | fn main() { 12 | let mut handles = Vec::new(); 13 | for i in 0..10 { 14 | let handle = thread::spawn(move || { 15 | let start = Instant::now(); 16 | thread::sleep(Duration::from_millis(250)); 17 | println!("Thread {i} done"); 18 | start.elapsed().as_millis() 19 | }); 20 | handles.push(handle); 21 | } 22 | 23 | let mut results = Vec::new(); 24 | for handle in handles { 25 | // Collect the results of all threads into the `results` vector. 26 | results.push(handle.join().unwrap()); 27 | } 28 | 29 | if results.len() != 10 { 30 | panic!("Oh no! Some thread isn't done yet!"); 31 | } 32 | 33 | println!(); 34 | for (i, result) in results.into_iter().enumerate() { 35 | println!("Thread {i} took {result}ms"); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /solutions/20_threads/threads2.rs: -------------------------------------------------------------------------------- 1 | // Building on the last exercise, we want all of the threads to complete their 2 | // work. But this time, the spawned threads need to be in charge of updating a 3 | // shared value: `JobStatus.jobs_done` 4 | 5 | use std::{ 6 | sync::{Arc, Mutex}, 7 | thread, 8 | time::Duration, 9 | }; 10 | 11 | struct JobStatus { 12 | jobs_done: u32, 13 | } 14 | 15 | fn main() { 16 | // `Arc` isn't enough if you want a **mutable** shared state. 17 | // We need to wrap the value with a `Mutex`. 18 | let status = Arc::new(Mutex::new(JobStatus { jobs_done: 0 })); 19 | // ^^^^^^^^^^^ ^ 20 | 21 | let mut handles = Vec::new(); 22 | for _ in 0..10 { 23 | let status_shared = Arc::clone(&status); 24 | let handle = thread::spawn(move || { 25 | thread::sleep(Duration::from_millis(250)); 26 | 27 | // Lock before you update a shared value. 28 | status_shared.lock().unwrap().jobs_done += 1; 29 | // ^^^^^^^^^^^^^^^^ 30 | }); 31 | handles.push(handle); 32 | } 33 | 34 | // Waiting for all jobs to complete. 35 | for handle in handles { 36 | handle.join().unwrap(); 37 | } 38 | 39 | println!("Jobs done: {}", status.lock().unwrap().jobs_done); 40 | // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 41 | } 42 | -------------------------------------------------------------------------------- /solutions/20_threads/threads3.rs: -------------------------------------------------------------------------------- 1 | use std::{sync::mpsc, thread, time::Duration}; 2 | 3 | struct Queue { 4 | first_half: Vec, 5 | second_half: Vec, 6 | } 7 | 8 | impl Queue { 9 | fn new() -> Self { 10 | Self { 11 | first_half: vec![1, 2, 3, 4, 5], 12 | second_half: vec![6, 7, 8, 9, 10], 13 | } 14 | } 15 | } 16 | 17 | fn send_tx(q: Queue, tx: mpsc::Sender) { 18 | // Clone the sender `tx` first. 19 | let tx_clone = tx.clone(); 20 | thread::spawn(move || { 21 | for val in q.first_half { 22 | println!("Sending {val:?}"); 23 | // Then use the clone in the first thread. This means that 24 | // `tx_clone` is moved to the first thread and `tx` to the second. 25 | tx_clone.send(val).unwrap(); 26 | thread::sleep(Duration::from_millis(250)); 27 | } 28 | }); 29 | 30 | thread::spawn(move || { 31 | for val in q.second_half { 32 | println!("Sending {val:?}"); 33 | tx.send(val).unwrap(); 34 | thread::sleep(Duration::from_millis(250)); 35 | } 36 | }); 37 | } 38 | 39 | fn main() { 40 | // You can optionally experiment here. 41 | } 42 | 43 | #[cfg(test)] 44 | mod tests { 45 | use super::*; 46 | 47 | #[test] 48 | fn threads3() { 49 | let (tx, rx) = mpsc::channel(); 50 | let queue = Queue::new(); 51 | 52 | send_tx(queue, tx); 53 | 54 | let mut received = Vec::with_capacity(10); 55 | for value in rx { 56 | received.push(value); 57 | } 58 | 59 | received.sort(); 60 | assert_eq!(received, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /solutions/21_macros/macros1.rs: -------------------------------------------------------------------------------- 1 | macro_rules! my_macro { 2 | () => { 3 | println!("Check out my macro!"); 4 | }; 5 | } 6 | 7 | fn main() { 8 | my_macro!(); 9 | // ^ 10 | } 11 | -------------------------------------------------------------------------------- /solutions/21_macros/macros2.rs: -------------------------------------------------------------------------------- 1 | // Moved the macro definition to be before its call. 2 | macro_rules! my_macro { 3 | () => { 4 | println!("Check out my macro!"); 5 | }; 6 | } 7 | 8 | fn main() { 9 | my_macro!(); 10 | } 11 | -------------------------------------------------------------------------------- /solutions/21_macros/macros3.rs: -------------------------------------------------------------------------------- 1 | // Added the attribute `macro_use` attribute. 2 | #[macro_use] 3 | mod macros { 4 | macro_rules! my_macro { 5 | () => { 6 | println!("Check out my macro!"); 7 | }; 8 | } 9 | } 10 | 11 | fn main() { 12 | my_macro!(); 13 | } 14 | -------------------------------------------------------------------------------- /solutions/21_macros/macros4.rs: -------------------------------------------------------------------------------- 1 | // Added semicolons to separate the macro arms. 2 | #[rustfmt::skip] 3 | macro_rules! my_macro { 4 | () => { 5 | println!("Check out my macro!"); 6 | }; 7 | ($val:expr) => { 8 | println!("Look at this other macro: {}", $val); 9 | }; 10 | } 11 | 12 | fn main() { 13 | my_macro!(); 14 | my_macro!(7777); 15 | } 16 | -------------------------------------------------------------------------------- /solutions/22_clippy/clippy1.rs: -------------------------------------------------------------------------------- 1 | // The Clippy tool is a collection of lints to analyze your code so you can 2 | // catch common mistakes and improve your Rust code. 3 | // 4 | // For these exercises, the code will fail to compile when there are Clippy 5 | // warnings. Check Clippy's suggestions from the output to solve the exercise. 6 | 7 | use std::f32::consts::PI; 8 | 9 | fn main() { 10 | // Use the more accurate `PI` constant. 11 | let pi = PI; 12 | let radius: f32 = 5.0; 13 | 14 | let area = pi * radius.powi(2); 15 | 16 | println!("The area of a circle with radius {radius:.2} is {area:.5}"); 17 | } 18 | -------------------------------------------------------------------------------- /solutions/22_clippy/clippy2.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | let mut res = 42; 3 | let option = Some(12); 4 | // Use `if-let` instead of iteration. 5 | if let Some(x) = option { 6 | res += x; 7 | } 8 | 9 | println!("{res}"); 10 | } 11 | -------------------------------------------------------------------------------- /solutions/22_clippy/clippy3.rs: -------------------------------------------------------------------------------- 1 | use std::mem; 2 | 3 | #[rustfmt::skip] 4 | #[allow(unused_variables, unused_assignments)] 5 | fn main() { 6 | let my_option: Option<&str> = None; 7 | // `unwrap` of an `Option` after checking if it is `None` will panic. 8 | // Use `if-let` instead. 9 | if let Some(value) = my_option { 10 | println!("{value}"); 11 | } 12 | 13 | // A comma was missing. 14 | let my_arr = &[ 15 | -1, -2, -3, 16 | -4, -5, -6, 17 | ]; 18 | println!("My array! Here it is: {my_arr:?}"); 19 | 20 | let mut my_empty_vec = vec![1, 2, 3, 4, 5]; 21 | // `resize` mutates a vector instead of returning a new one. 22 | // `resize(0, …)` clears a vector, so it is better to use `clear`. 23 | my_empty_vec.clear(); 24 | println!("This Vec is empty, see? {my_empty_vec:?}"); 25 | 26 | let mut value_a = 45; 27 | let mut value_b = 66; 28 | // Use `mem::swap` to correctly swap two values. 29 | mem::swap(&mut value_a, &mut value_b); 30 | println!("value a: {value_a}; value b: {value_b}"); 31 | } 32 | -------------------------------------------------------------------------------- /solutions/23_conversions/as_ref_mut.rs: -------------------------------------------------------------------------------- 1 | // AsRef and AsMut allow for cheap reference-to-reference conversions. Read more 2 | // about them at https://doc.rust-lang.org/std/convert/trait.AsRef.html and 3 | // https://doc.rust-lang.org/std/convert/trait.AsMut.html, respectively. 4 | 5 | // Obtain the number of bytes (not characters) in the given argument 6 | // (`.len()` returns the number of bytes in a string). 7 | fn byte_counter>(arg: T) -> usize { 8 | arg.as_ref().len() 9 | } 10 | 11 | // Obtain the number of characters (not bytes) in the given argument. 12 | fn char_counter>(arg: T) -> usize { 13 | arg.as_ref().chars().count() 14 | } 15 | 16 | // Squares a number using `as_mut()`. 17 | fn num_sq>(arg: &mut T) { 18 | let arg = arg.as_mut(); 19 | *arg *= *arg; 20 | } 21 | 22 | fn main() { 23 | // You can optionally experiment here. 24 | } 25 | 26 | #[cfg(test)] 27 | mod tests { 28 | use super::*; 29 | 30 | #[test] 31 | fn different_counts() { 32 | let s = "Café au lait"; 33 | assert_ne!(char_counter(s), byte_counter(s)); 34 | } 35 | 36 | #[test] 37 | fn same_counts() { 38 | let s = "Cafe au lait"; 39 | assert_eq!(char_counter(s), byte_counter(s)); 40 | } 41 | 42 | #[test] 43 | fn different_counts_using_string() { 44 | let s = String::from("Café au lait"); 45 | assert_ne!(char_counter(s.clone()), byte_counter(s)); 46 | } 47 | 48 | #[test] 49 | fn same_counts_using_string() { 50 | let s = String::from("Cafe au lait"); 51 | assert_eq!(char_counter(s.clone()), byte_counter(s)); 52 | } 53 | 54 | #[test] 55 | fn mut_box() { 56 | let mut num: Box = Box::new(3); 57 | num_sq(&mut num); 58 | assert_eq!(*num, 9); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /solutions/23_conversions/using_as.rs: -------------------------------------------------------------------------------- 1 | // Type casting in Rust is done via the usage of the `as` operator. 2 | // Note that the `as` operator is not only used when type casting. It also helps 3 | // with renaming imports. 4 | 5 | fn average(values: &[f64]) -> f64 { 6 | let total = values.iter().sum::(); 7 | total / values.len() as f64 8 | // ^^^^^^ 9 | } 10 | 11 | fn main() { 12 | let values = [3.5, 0.3, 13.0, 11.7]; 13 | println!("{}", average(&values)); 14 | } 15 | 16 | #[cfg(test)] 17 | mod tests { 18 | use super::*; 19 | 20 | #[test] 21 | fn returns_proper_type_and_value() { 22 | assert_eq!(average(&[3.5, 0.3, 13.0, 11.7]), 7.125); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /solutions/README.md: -------------------------------------------------------------------------------- 1 | # Official Rustlings solutions 2 | 3 | Before you finish an exercise, its solution file will only contain an empty `main` function. 4 | The content of this file will be automatically replaced by the actual solution once you finish the exercise. 5 | 6 | Note that these solutions are often only _one possibility_ to solve an exercise. 7 | -------------------------------------------------------------------------------- /solutions/quizzes/quiz1.rs: -------------------------------------------------------------------------------- 1 | // Mary is buying apples. The price of an apple is calculated as follows: 2 | // - An apple costs 2 rustbucks. 3 | // - However, if Mary buys more than 40 apples, the price of each apple in the 4 | // entire order is reduced to only 1 rustbuck! 5 | 6 | fn calculate_price_of_apples(n_apples: u64) -> u64 { 7 | if n_apples > 40 { 8 | n_apples 9 | } else { 10 | 2 * n_apples 11 | } 12 | } 13 | 14 | fn main() { 15 | // You can optionally experiment here. 16 | } 17 | 18 | // Don't change the tests! 19 | #[cfg(test)] 20 | mod tests { 21 | use super::*; 22 | 23 | #[test] 24 | fn verify_test() { 25 | assert_eq!(calculate_price_of_apples(35), 70); 26 | assert_eq!(calculate_price_of_apples(40), 80); 27 | assert_eq!(calculate_price_of_apples(41), 41); 28 | assert_eq!(calculate_price_of_apples(65), 65); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /solutions/quizzes/quiz3.rs: -------------------------------------------------------------------------------- 1 | // An imaginary magical school has a new report card generation system written 2 | // in Rust! Currently, the system only supports creating report cards where the 3 | // student's grade is represented numerically (e.g. 1.0 -> 5.5). However, the 4 | // school also issues alphabetical grades (A+ -> F-) and needs to be able to 5 | // print both types of report card! 6 | // 7 | // Make the necessary code changes in the struct `ReportCard` and the impl 8 | // block to support alphabetical report cards in addition to numerical ones. 9 | 10 | use std::fmt::Display; 11 | 12 | // Make the struct generic over `T`. 13 | struct ReportCard { 14 | // ^^^ 15 | grade: T, 16 | // ^ 17 | student_name: String, 18 | student_age: u8, 19 | } 20 | 21 | // To be able to print the grade, it has to implement the `Display` trait. 22 | impl ReportCard { 23 | // ^^^^^^^ require that `T` implements `Display`. 24 | fn print(&self) -> String { 25 | format!( 26 | "{} ({}) - achieved a grade of {}", 27 | &self.student_name, &self.student_age, &self.grade, 28 | ) 29 | } 30 | } 31 | 32 | fn main() { 33 | // You can optionally experiment here. 34 | } 35 | 36 | #[cfg(test)] 37 | mod tests { 38 | use super::*; 39 | 40 | #[test] 41 | fn generate_numeric_report_card() { 42 | let report_card = ReportCard { 43 | grade: 2.1, 44 | student_name: "Tom Wriggle".to_string(), 45 | student_age: 12, 46 | }; 47 | assert_eq!( 48 | report_card.print(), 49 | "Tom Wriggle (12) - achieved a grade of 2.1", 50 | ); 51 | } 52 | 53 | #[test] 54 | fn generate_alphabetic_report_card() { 55 | let report_card = ReportCard { 56 | grade: "A+", 57 | student_name: "Gary Plotter".to_string(), 58 | student_age: 11, 59 | }; 60 | assert_eq!( 61 | report_card.print(), 62 | "Gary Plotter (11) - achieved a grade of A+", 63 | ); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/dev.rs: -------------------------------------------------------------------------------- 1 | use anyhow::{Context, Result, bail}; 2 | use clap::Subcommand; 3 | use std::path::PathBuf; 4 | 5 | mod check; 6 | mod new; 7 | mod update; 8 | 9 | #[derive(Subcommand)] 10 | pub enum DevCommands { 11 | /// Create a new project for community exercises 12 | New { 13 | /// The path to create the project in 14 | path: PathBuf, 15 | /// Don't try to initialize a Git repository in the project directory 16 | #[arg(long)] 17 | no_git: bool, 18 | }, 19 | /// Run checks on the exercises 20 | Check { 21 | /// Require that every exercise has a solution 22 | #[arg(short, long)] 23 | require_solutions: bool, 24 | }, 25 | /// Update the `Cargo.toml` file for the exercises 26 | Update, 27 | } 28 | 29 | impl DevCommands { 30 | pub fn run(self) -> Result<()> { 31 | match self { 32 | Self::New { path, no_git } => { 33 | if cfg!(debug_assertions) { 34 | bail!("Disabled in the debug build"); 35 | } 36 | 37 | new::new(&path, no_git).context(INIT_ERR) 38 | } 39 | Self::Check { require_solutions } => check::check(require_solutions), 40 | Self::Update => update::update(), 41 | } 42 | } 43 | } 44 | 45 | const INIT_ERR: &str = "Initialization failed. 46 | After resolving the issue, delete the `rustlings` directory (if it was created) and try again"; 47 | -------------------------------------------------------------------------------- /src/dev/update.rs: -------------------------------------------------------------------------------- 1 | use anyhow::{Context, Result}; 2 | use std::fs; 3 | 4 | use crate::{ 5 | cargo_toml::updated_cargo_toml, 6 | info_file::{ExerciseInfo, InfoFile}, 7 | }; 8 | 9 | // Update the `Cargo.toml` file. 10 | fn update_cargo_toml( 11 | exercise_infos: &[ExerciseInfo], 12 | cargo_toml_path: &str, 13 | exercise_path_prefix: &[u8], 14 | ) -> Result<()> { 15 | let current_cargo_toml = fs::read_to_string(cargo_toml_path) 16 | .with_context(|| format!("Failed to read the file `{cargo_toml_path}`"))?; 17 | 18 | let updated_cargo_toml = 19 | updated_cargo_toml(exercise_infos, ¤t_cargo_toml, exercise_path_prefix)?; 20 | 21 | fs::write(cargo_toml_path, updated_cargo_toml) 22 | .context("Failed to write the `Cargo.toml` file")?; 23 | 24 | Ok(()) 25 | } 26 | 27 | pub fn update() -> Result<()> { 28 | let info_file = InfoFile::parse()?; 29 | 30 | if cfg!(debug_assertions) { 31 | // A hack to make `cargo dev update` work when developing Rustlings. 32 | update_cargo_toml(&info_file.exercises, "dev/Cargo.toml", b"../") 33 | .context("Failed to update the file `dev/Cargo.toml`")?; 34 | 35 | println!("Updated `dev/Cargo.toml`"); 36 | } else { 37 | update_cargo_toml(&info_file.exercises, "Cargo.toml", &[]) 38 | .context("Failed to update the file `Cargo.toml`")?; 39 | 40 | println!("Updated `Cargo.toml`"); 41 | } 42 | 43 | Ok(()) 44 | } 45 | -------------------------------------------------------------------------------- /src/run.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use crossterm::{ 3 | QueueableCommand, 4 | style::{Color, ResetColor, SetForegroundColor}, 5 | }; 6 | use std::{ 7 | io::{self, Write}, 8 | process::ExitCode, 9 | }; 10 | 11 | use crate::{ 12 | app_state::{AppState, ExercisesProgress}, 13 | exercise::{OUTPUT_CAPACITY, RunnableExercise, solution_link_line}, 14 | }; 15 | 16 | pub fn run(app_state: &mut AppState) -> Result { 17 | let exercise = app_state.current_exercise(); 18 | let mut output = Vec::with_capacity(OUTPUT_CAPACITY); 19 | let success = exercise.run_exercise(Some(&mut output), app_state.cmd_runner())?; 20 | 21 | let mut stdout = io::stdout().lock(); 22 | stdout.write_all(&output)?; 23 | 24 | if !success { 25 | app_state.set_pending(app_state.current_exercise_ind())?; 26 | 27 | stdout.write_all(b"Ran ")?; 28 | app_state 29 | .current_exercise() 30 | .terminal_file_link(&mut stdout)?; 31 | stdout.write_all(b" with errors\n")?; 32 | 33 | return Ok(ExitCode::FAILURE); 34 | } 35 | 36 | stdout.queue(SetForegroundColor(Color::Green))?; 37 | stdout.write_all("✓ Successfully ran ".as_bytes())?; 38 | stdout.write_all(exercise.path.as_bytes())?; 39 | stdout.queue(ResetColor)?; 40 | stdout.write_all(b"\n")?; 41 | 42 | if let Some(solution_path) = app_state.current_solution_path()? { 43 | stdout.write_all(b"\n")?; 44 | solution_link_line(&mut stdout, &solution_path)?; 45 | stdout.write_all(b"\n")?; 46 | } 47 | 48 | match app_state.done_current_exercise::(&mut stdout)? { 49 | ExercisesProgress::NewPending | ExercisesProgress::CurrentPending => { 50 | stdout.write_all(b"Next exercise: ")?; 51 | app_state 52 | .current_exercise() 53 | .terminal_file_link(&mut stdout)?; 54 | stdout.write_all(b"\n")?; 55 | } 56 | ExercisesProgress::AllDone => (), 57 | } 58 | 59 | Ok(ExitCode::SUCCESS) 60 | } 61 | -------------------------------------------------------------------------------- /src/watch/terminal_event.rs: -------------------------------------------------------------------------------- 1 | use crossterm::event::{self, Event, KeyCode, KeyEventKind}; 2 | use std::sync::{ 3 | atomic::Ordering::Relaxed, 4 | mpsc::{Receiver, Sender}, 5 | }; 6 | 7 | use super::{EXERCISE_RUNNING, WatchEvent}; 8 | 9 | pub enum InputEvent { 10 | Next, 11 | Run, 12 | Hint, 13 | List, 14 | CheckAll, 15 | Reset, 16 | Quit, 17 | } 18 | 19 | pub fn terminal_event_handler( 20 | sender: Sender, 21 | unpause_receiver: Receiver<()>, 22 | manual_run: bool, 23 | ) { 24 | let last_watch_event = loop { 25 | match event::read() { 26 | Ok(Event::Key(key)) => { 27 | match key.kind { 28 | KeyEventKind::Release | KeyEventKind::Repeat => continue, 29 | KeyEventKind::Press => (), 30 | } 31 | 32 | if EXERCISE_RUNNING.load(Relaxed) { 33 | continue; 34 | } 35 | 36 | let input_event = match key.code { 37 | KeyCode::Char('n') => InputEvent::Next, 38 | KeyCode::Char('r') if manual_run => InputEvent::Run, 39 | KeyCode::Char('h') => InputEvent::Hint, 40 | KeyCode::Char('l') => break WatchEvent::Input(InputEvent::List), 41 | KeyCode::Char('c') => InputEvent::CheckAll, 42 | KeyCode::Char('x') => { 43 | if sender.send(WatchEvent::Input(InputEvent::Reset)).is_err() { 44 | return; 45 | } 46 | 47 | // Pause input until quitting the confirmation prompt. 48 | if unpause_receiver.recv().is_err() { 49 | return; 50 | }; 51 | 52 | continue; 53 | } 54 | KeyCode::Char('q') => break WatchEvent::Input(InputEvent::Quit), 55 | _ => continue, 56 | }; 57 | 58 | if sender.send(WatchEvent::Input(input_event)).is_err() { 59 | return; 60 | } 61 | } 62 | Ok(Event::Resize(width, _)) => { 63 | if sender.send(WatchEvent::TerminalResize { width }).is_err() { 64 | return; 65 | } 66 | } 67 | Ok(Event::FocusGained | Event::FocusLost | Event::Mouse(_)) => continue, 68 | Err(e) => break WatchEvent::TerminalEventErr(e), 69 | } 70 | }; 71 | 72 | let _ = sender.send(last_watch_event); 73 | } 74 | -------------------------------------------------------------------------------- /tests/test_exercises/dev/Cargo.toml: -------------------------------------------------------------------------------- 1 | bin = [ 2 | { name = "compilation_success", path = "../exercises/compilation_success.rs" }, 3 | { name = "compilation_failure", path = "../exercises/compilation_failure.rs" }, 4 | { name = "test_success", path = "../exercises/test_success.rs" }, 5 | { name = "test_failure", path = "../exercises/test_failure.rs" }, 6 | ] 7 | 8 | [package] 9 | name = "test_exercises" 10 | edition = "2024" 11 | publish = false 12 | -------------------------------------------------------------------------------- /tests/test_exercises/exercises/compilation_failure.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | let 3 | } -------------------------------------------------------------------------------- /tests/test_exercises/exercises/compilation_success.rs: -------------------------------------------------------------------------------- 1 | fn main() {} 2 | -------------------------------------------------------------------------------- /tests/test_exercises/exercises/not_in_info.rs: -------------------------------------------------------------------------------- 1 | fn main() {} 2 | -------------------------------------------------------------------------------- /tests/test_exercises/exercises/test_failure.rs: -------------------------------------------------------------------------------- 1 | fn main() {} 2 | 3 | #[cfg(test)] 4 | mod tests { 5 | #[test] 6 | fn fails() { 7 | assert!(false); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /tests/test_exercises/exercises/test_success.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | println!("Output from `main` function"); 3 | } 4 | 5 | #[cfg(test)] 6 | mod tests { 7 | #[test] 8 | fn passes() {} 9 | } 10 | -------------------------------------------------------------------------------- /tests/test_exercises/info.toml: -------------------------------------------------------------------------------- 1 | format_version = 1 2 | 3 | [[exercises]] 4 | name = "compilation_success" 5 | test = false 6 | hint = "" 7 | 8 | [[exercises]] 9 | name = "compilation_failure" 10 | test = false 11 | hint = "" 12 | 13 | [[exercises]] 14 | name = "test_success" 15 | hint = "" 16 | 17 | [[exercises]] 18 | name = "test_failure" 19 | hint = "The answer to everything: 42" 20 | -------------------------------------------------------------------------------- /website/.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules/ 2 | /package-lock.json 3 | 4 | /public/ 5 | 6 | /static/main.css 7 | /static/processed_images/ 8 | -------------------------------------------------------------------------------- /website/config.toml: -------------------------------------------------------------------------------- 1 | base_url = "https://rustlings.rust-lang.org" 2 | title = "Rustlings" 3 | description = "Small exercises to get you used to reading and writing Rust code!" 4 | 5 | compile_sass = false 6 | build_search_index = false 7 | 8 | [markdown] 9 | highlight_code = true 10 | highlight_theme = "dracula" 11 | 12 | insert_anchor_links = "heading" 13 | 14 | [extra] 15 | logo_path = "images/happy_ferris.svg" 16 | 17 | [[extra.menu_items]] 18 | name = "Rustlings" 19 | url = "@/_index.md" 20 | [[extra.menu_items]] 21 | name = "Setup" 22 | url = "@/setup/index.md" 23 | [[extra.menu_items]] 24 | name = "Usage" 25 | url = "@/usage/index.md" 26 | [[extra.menu_items]] 27 | name = "Community Exercises" 28 | url = "@/community-exercises/index.md" 29 | [[extra.menu_items]] 30 | name = "Q&A" 31 | url = "https://github.com/rust-lang/rustlings/discussions/categories/q-a?discussions_q=" 32 | 33 | [[extra.footer_items]] 34 | name = "Repository" 35 | url = "https://github.com/rust-lang/rustlings" 36 | [[extra.footer_items]] 37 | name = "Changelog" 38 | url = "https://github.com/rust-lang/rustlings/blob/main/CHANGELOG.md" 39 | [[extra.footer_items]] 40 | name = "MIT License" 41 | url = "https://github.com/rust-lang/rustlings/blob/main/LICENSE" 42 | -------------------------------------------------------------------------------- /website/content/_index.md: -------------------------------------------------------------------------------- 1 | +++ 2 | +++ 3 | 4 | Small exercises to get you used to reading and writing [Rust](https://www.rust-lang.org) code - _Recommended in parallel to reading [the official Rust book](https://doc.rust-lang.org/book) 📚️_ 5 | 6 | 7 | 8 | ## Quick start 9 | 10 | ```bash 11 | # Installation 12 | cargo install rustlings 13 | # Initialization 14 | rustlings init 15 | # Moving into new directory 16 | cd rustlings 17 | # Starting Rustlings 18 | rustlings 19 | ``` 20 | 21 | Visit the [**setup**](@/setup/index.md) page for more details 🧰 22 | -------------------------------------------------------------------------------- /website/input.css: -------------------------------------------------------------------------------- 1 | @import 'tailwindcss'; 2 | 3 | @layer base { 4 | h1 { 5 | @apply text-4xl mt-3 mb-3 font-bold; 6 | } 7 | h2 { 8 | @apply text-3xl mt-4 mb-1.5 font-bold; 9 | } 10 | h3 { 11 | @apply text-2xl mt-5 mb-1.5 font-bold; 12 | } 13 | h4 { 14 | @apply text-xl mt-6 mb-1.5 font-bold; 15 | } 16 | p { 17 | @apply mb-2; 18 | } 19 | a { 20 | @apply text-[#FFC832] underline hover:decoration-orange-400 transition duration-300; 21 | } 22 | ul { 23 | @apply mt-2 mb-3 ml-1 list-disc list-inside marker:text-sky-600; 24 | } 25 | ol { 26 | @apply mt-2 mb-3 ml-1 list-decimal list-inside marker:text-sky-500; 27 | } 28 | li { 29 | @apply my-0.5; 30 | } 31 | code { 32 | @apply bg-white/10 px-1 pb-px pt-1 rounded-md; 33 | } 34 | pre code { 35 | @apply bg-inherit p-0 text-inherit; 36 | } 37 | hr { 38 | @apply my-5 rounded-full; 39 | } 40 | img { 41 | @apply md:w-3/4 lg:w-3/5; 42 | } 43 | blockquote { 44 | @apply px-3 pt-2 pb-0.5 mb-4 mt-2 border-s-4 border-white/80 bg-white/7 rounded-sm; 45 | } 46 | 47 | pre { 48 | @apply px-2 pt-2 pb-px overflow-x-auto text-sm sm:text-base rounded-sm mt-2 mb-4 after:content-[attr(data-lang)] after:text-[8px] after:opacity-40 selection:bg-white/15; 49 | } 50 | pre code mark { 51 | @apply pb-0.5 pt-1 pr-px text-inherit rounded-xs; 52 | } 53 | } 54 | 55 | -------------------------------------------------------------------------------- /website/justfile: -------------------------------------------------------------------------------- 1 | zola: 2 | zola serve --open 3 | 4 | tailwind: 5 | npx @tailwindcss/cli -w -i input.css -o static/main.css 6 | -------------------------------------------------------------------------------- /website/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "@tailwindcss/cli": "^4.1" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /website/templates/404.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block content %} 4 |
5 |

DON'T PANIC!

6 |

404: Page not found!

7 | 8 | 11 | 12 | Back to homepage 13 |
14 | {% endblock %} 15 | -------------------------------------------------------------------------------- /website/templates/anchor-link.html: -------------------------------------------------------------------------------- 1 | 3 | -------------------------------------------------------------------------------- /website/templates/index.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block content %} 4 |
5 |

Rustlings

6 | 7 | {{ section.content | safe }} 8 |
9 | {% endblock %} 10 | -------------------------------------------------------------------------------- /website/templates/page.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block content %} 4 |
5 |

{{ page.title }}

6 | 7 |
8 | 35 |
36 | 37 | {{ page.content | safe }} 38 |
39 | {% endblock %} 40 | -------------------------------------------------------------------------------- /website/templates/shortcodes/details.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | {{ summary | safe }} (click to expand) 4 | 5 | 6 |
7 | {{ body | markdown | safe }} 8 |
9 |
10 | --------------------------------------------------------------------------------