Repository: mainmatter/100-exercises-to-learn-rust Branch: main Commit: 8791feb495a9 Files: 247 Total size: 205.6 KB Directory structure: gitextract_716k_gfd/ ├── .github/ │ └── workflows/ │ └── ci.yml ├── .gitignore ├── .wr.toml ├── Cargo.toml ├── README.md ├── dprint.json ├── exercises/ │ ├── 01_intro/ │ │ ├── 00_welcome/ │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ └── lib.rs │ │ └── 01_syntax/ │ │ ├── Cargo.toml │ │ └── src/ │ │ └── lib.rs │ ├── 02_basic_calculator/ │ │ ├── 00_intro/ │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ └── lib.rs │ │ ├── 01_integers/ │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ └── lib.rs │ │ ├── 02_variables/ │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ └── lib.rs │ │ ├── 03_if_else/ │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ └── lib.rs │ │ ├── 04_panics/ │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ └── lib.rs │ │ ├── 05_factorial/ │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ └── lib.rs │ │ ├── 06_while/ │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ └── lib.rs │ │ ├── 07_for/ │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ └── lib.rs │ │ ├── 08_overflow/ │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ └── lib.rs │ │ ├── 09_saturating/ │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ └── lib.rs │ │ └── 10_as_casting/ │ │ ├── Cargo.toml │ │ └── src/ │ │ └── lib.rs │ ├── 03_ticket_v1/ │ │ ├── 00_intro/ │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ └── lib.rs │ │ ├── 01_struct/ │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ └── lib.rs │ │ ├── 02_validation/ │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ └── lib.rs │ │ ├── 03_modules/ │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ └── lib.rs │ │ ├── 04_visibility/ │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ └── lib.rs │ │ ├── 05_encapsulation/ │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ └── lib.rs │ │ ├── 06_ownership/ │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ └── lib.rs │ │ ├── 07_setters/ │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ └── lib.rs │ │ ├── 08_stack/ │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ └── lib.rs │ │ ├── 09_heap/ │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ └── lib.rs │ │ ├── 10_references_in_memory/ │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ └── lib.rs │ │ ├── 11_destructor/ │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ └── lib.rs │ │ └── 12_outro/ │ │ ├── Cargo.toml │ │ ├── src/ │ │ │ └── lib.rs │ │ └── tests/ │ │ └── integration.rs │ ├── 04_traits/ │ │ ├── 00_intro/ │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ └── lib.rs │ │ ├── 01_trait/ │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ └── lib.rs │ │ ├── 02_orphan_rule/ │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ └── lib.rs │ │ ├── 03_operator_overloading/ │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ └── lib.rs │ │ ├── 04_derive/ │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ └── lib.rs │ │ ├── 05_trait_bounds/ │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ └── lib.rs │ │ ├── 06_str_slice/ │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ └── lib.rs │ │ ├── 07_deref/ │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ └── lib.rs │ │ ├── 08_sized/ │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ └── lib.rs │ │ ├── 09_from/ │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ └── lib.rs │ │ ├── 10_assoc_vs_generic/ │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ └── lib.rs │ │ ├── 11_clone/ │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ └── lib.rs │ │ ├── 12_copy/ │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ └── lib.rs │ │ ├── 13_drop/ │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ └── lib.rs │ │ └── 14_outro/ │ │ ├── Cargo.toml │ │ ├── src/ │ │ │ └── lib.rs │ │ └── tests/ │ │ └── integration.rs │ ├── 05_ticket_v2/ │ │ ├── 00_intro/ │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ └── lib.rs │ │ ├── 01_enum/ │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ └── lib.rs │ │ ├── 02_match/ │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ └── lib.rs │ │ ├── 03_variants_with_data/ │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ └── lib.rs │ │ ├── 04_if_let/ │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ └── lib.rs │ │ ├── 05_nullability/ │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ └── lib.rs │ │ ├── 06_fallibility/ │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ └── lib.rs │ │ ├── 07_unwrap/ │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ └── lib.rs │ │ ├── 08_error_enums/ │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ └── lib.rs │ │ ├── 09_error_trait/ │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ └── lib.rs │ │ ├── 10_packages/ │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ └── main.rs │ │ ├── 11_dependencies/ │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ └── lib.rs │ │ ├── 12_thiserror/ │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ └── lib.rs │ │ ├── 13_try_from/ │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ └── lib.rs │ │ ├── 14_source/ │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ ├── lib.rs │ │ │ └── status.rs │ │ └── 15_outro/ │ │ ├── Cargo.toml │ │ └── src/ │ │ ├── description.rs │ │ ├── lib.rs │ │ ├── status.rs │ │ └── title.rs │ ├── 06_ticket_management/ │ │ ├── 00_intro/ │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ └── lib.rs │ │ ├── 01_arrays/ │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ └── lib.rs │ │ ├── 02_vec/ │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ └── lib.rs │ │ ├── 03_resizing/ │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ └── lib.rs │ │ ├── 04_iterators/ │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ └── lib.rs │ │ ├── 05_iter/ │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ └── lib.rs │ │ ├── 06_lifetimes/ │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ └── lib.rs │ │ ├── 07_combinators/ │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ └── lib.rs │ │ ├── 08_impl_trait/ │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ └── lib.rs │ │ ├── 09_impl_trait_2/ │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ └── lib.rs │ │ ├── 10_slices/ │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ └── lib.rs │ │ ├── 11_mutable_slices/ │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ └── lib.rs │ │ ├── 12_two_states/ │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ └── lib.rs │ │ ├── 13_index/ │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ └── lib.rs │ │ ├── 14_index_mut/ │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ └── lib.rs │ │ ├── 15_hashmap/ │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ └── lib.rs │ │ └── 16_btreemap/ │ │ ├── Cargo.toml │ │ └── src/ │ │ └── lib.rs │ ├── 07_threads/ │ │ ├── 00_intro/ │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ └── lib.rs │ │ ├── 01_threads/ │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ └── lib.rs │ │ ├── 02_static/ │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ └── lib.rs │ │ ├── 03_leak/ │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ └── lib.rs │ │ ├── 04_scoped_threads/ │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ └── lib.rs │ │ ├── 05_channels/ │ │ │ ├── Cargo.toml │ │ │ ├── src/ │ │ │ │ ├── data.rs │ │ │ │ ├── lib.rs │ │ │ │ └── store.rs │ │ │ └── tests/ │ │ │ └── insert.rs │ │ ├── 06_interior_mutability/ │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ └── lib.rs │ │ ├── 07_ack/ │ │ │ ├── Cargo.toml │ │ │ ├── src/ │ │ │ │ ├── data.rs │ │ │ │ ├── lib.rs │ │ │ │ └── store.rs │ │ │ └── tests/ │ │ │ └── insert.rs │ │ ├── 08_client/ │ │ │ ├── Cargo.toml │ │ │ ├── src/ │ │ │ │ ├── data.rs │ │ │ │ ├── lib.rs │ │ │ │ └── store.rs │ │ │ └── tests/ │ │ │ └── insert.rs │ │ ├── 09_bounded/ │ │ │ ├── Cargo.toml │ │ │ ├── src/ │ │ │ │ ├── data.rs │ │ │ │ ├── lib.rs │ │ │ │ └── store.rs │ │ │ └── tests/ │ │ │ └── insert.rs │ │ ├── 10_patch/ │ │ │ ├── Cargo.toml │ │ │ ├── src/ │ │ │ │ ├── data.rs │ │ │ │ ├── lib.rs │ │ │ │ └── store.rs │ │ │ └── tests/ │ │ │ └── check.rs │ │ ├── 11_locks/ │ │ │ ├── Cargo.toml │ │ │ ├── src/ │ │ │ │ ├── data.rs │ │ │ │ ├── lib.rs │ │ │ │ └── store.rs │ │ │ └── tests/ │ │ │ └── check.rs │ │ ├── 12_rw_lock/ │ │ │ ├── Cargo.toml │ │ │ ├── src/ │ │ │ │ ├── data.rs │ │ │ │ ├── lib.rs │ │ │ │ └── store.rs │ │ │ └── tests/ │ │ │ └── check.rs │ │ ├── 13_without_channels/ │ │ │ ├── Cargo.toml │ │ │ ├── src/ │ │ │ │ ├── data.rs │ │ │ │ ├── lib.rs │ │ │ │ └── store.rs │ │ │ └── tests/ │ │ │ └── check.rs │ │ └── 14_sync/ │ │ ├── Cargo.toml │ │ └── src/ │ │ └── lib.rs │ └── 08_futures/ │ ├── 00_intro/ │ │ ├── Cargo.toml │ │ └── src/ │ │ └── lib.rs │ ├── 01_async_fn/ │ │ ├── Cargo.toml │ │ └── src/ │ │ └── lib.rs │ ├── 02_spawn/ │ │ ├── Cargo.toml │ │ └── src/ │ │ └── lib.rs │ ├── 03_runtime/ │ │ ├── Cargo.toml │ │ └── src/ │ │ └── lib.rs │ ├── 04_future/ │ │ ├── Cargo.toml │ │ └── src/ │ │ └── lib.rs │ ├── 05_blocking/ │ │ ├── Cargo.toml │ │ └── src/ │ │ └── lib.rs │ ├── 06_async_aware_primitives/ │ │ ├── Cargo.toml │ │ └── src/ │ │ └── lib.rs │ ├── 07_cancellation/ │ │ ├── Cargo.toml │ │ └── src/ │ │ └── lib.rs │ └── 08_outro/ │ ├── Cargo.toml │ └── src/ │ └── lib.rs ├── helpers/ │ ├── common/ │ │ ├── Cargo.toml │ │ └── src/ │ │ └── lib.rs │ ├── json2redirects.sh │ ├── mdbook-exercise-linker/ │ │ ├── Cargo.toml │ │ └── src/ │ │ ├── lib.rs │ │ └── main.rs │ ├── mdbook-link-shortener/ │ │ ├── Cargo.toml │ │ └── src/ │ │ ├── lib.rs │ │ └── main.rs │ └── ticket_fields/ │ ├── Cargo.toml │ └── src/ │ ├── description.rs │ ├── lib.rs │ ├── test_helpers.rs │ └── title.rs └── site/ └── _redirects ================================================ FILE CONTENTS ================================================ ================================================ FILE: .github/workflows/ci.yml ================================================ name: "CI" on: push: branches: - main pull_request: branches: - main workflow_dispatch: schedule: # First day of a month - cron: '0 0 1 * *' jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 with: fetch-depth: 0 - name: Get Core Sans uses: actions/checkout@v4 if: "!github.event.pull_request.head.repo.fork" with: fetch-depth: 0 repository: mainmatter/core-sans-a-fonts ssh-key: ${{ secrets.SSH_PRIVATE_KEY }} path: core-sans-a-fonts - name: Install Core Sans Font if: "!github.event.pull_request.head.repo.fork" run: | sudo cp -r core-sans-a-fonts/* /usr/local/share/fonts/ sudo fc-cache -f -v fc-list | grep "Core Sans" - name: Use Fallback font for fork PRs if: "github.event.pull_request.head.repo.fork" run: | sed -i 's/"BoldFont=CoreSansA65.ttf",//g' book/book.toml sed -i 's/"ItalicFont=CoreSansA45It.ttf",//g' book/book.toml sed -i 's/"BoldItalicFont=CoreSansA65It.ttf",//g' book/book.toml sed -i 's/CoreSansA45.ttf/Open Sans:style=Regular/g' book/book.toml - uses: actions-rust-lang/setup-rust-toolchain@v1 - name: Install exercise plugin run: cargo install --path helpers/mdbook-exercise-linker - name: Install link shortener plugin run: cargo install --path helpers/mdbook-link-shortener - name: Install mdbook-pandoc, calibre, pdftk and related dependencies run: | cargo install mdbook-pandoc --locked --version 0.7.1 sudo apt-get update sudo apt-get install -y fonts-noto fonts-open-sans calibre pdftk sudo fc-cache -f -v export PANDOC_VERSION=3.3 curl -LsSf https://github.com/jgm/pandoc/releases/download/${PANDOC_VERSION}/pandoc-${PANDOC_VERSION}-linux-amd64.tar.gz | tar zxf - echo "$PWD/pandoc-${PANDOC_VERSION}/bin" >> $GITHUB_PATH shell: bash - name: Setup TeX Live uses: TeX-Live/setup-texlive-action@v3 with: packages: scheme-basic luatex lualatex-math luacolor luatexbase luaotfload framed unicode-math xcolor geometry longtable booktabs array lua-ul etoolbox fancyvrb footnote selnolig natbib csquotes bookmark xurl amsmath setspace iftex - name: Check `tlmgr` version run: tlmgr --version - uses: taiki-e/install-action@v2 with: tool: mdbook - name: Build book env: LINK_SHORTENER_VERIFY: "true" run: | cd book mdbook build - name: Add cover and back to downloadable PDF run: | pdftk book/assets/cover.pdf book/book/pandoc/pdf/100-exercises-to-learn-rust.pdf book/assets/back.pdf cat output book/book/pandoc/pdf/100-exercises-to-learn-rust-with-cover.pdf mv book/book/pandoc/pdf/100-exercises-to-learn-rust-with-cover.pdf book/book/pandoc/pdf/100-exercises-to-learn-rust.pdf - name: Convert HTML to ePUB run: | cd book/book/pandoc/html sed -i 's|\\newpage{=latex}||g' 100-exercises-to-learn-rust.html ebook-convert 100-exercises-to-learn-rust.html 100-exercises-to-learn-rust.epub \ --embed-all-fonts \ --subset-embedded-fonts - name: Link Checker uses: lycheeverse/lychee-action@v1 with: fail: true args: | --exclude-loopback --require-https --no-progress book/book/html/ # Upload the HTML book as an artifact - uses: actions/upload-artifact@v4 with: name: book # When you support multiple formats, the output directory changes # to include the format in its path. path: book/book/html - uses: actions/upload-artifact@v4 with: name: online-pdf path: book/book/pandoc/pdf/100-exercises-to-learn-rust.pdf - uses: actions/upload-artifact@v4 with: name: paperback path: book/book/pandoc/paperback/100-exercises-to-learn-rust.pdf - uses: actions/upload-artifact@v4 with: name: ePUB path: book/book/pandoc/html/100-exercises-to-learn-rust.epub is_fresh: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - run: sudo apt-get update && sudo apt-get install -y jq - run: | ./helpers/json2redirects.sh book/link2alias.json > site/_redirects # Verify nothing has changed, meaning that the redirect file is up-to-date - run: | git diff --exit-code site/_redirects gravity: runs-on: ubuntu-latest needs: [build] steps: - uses: actions/download-artifact@v4 with: path: book pattern: online-pdf - uses: pnpm/action-setup@v4 with: version: 9 - run: ls -las ./book - name: Run Gravity run: pnpm dlx @gravityci/cli "./book/**/*" env: GRAVITY_TOKEN: ${{ secrets.GRAVITY_TOKEN }} formatter: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - uses: dprint/check@v2.2 ================================================ FILE: .gitignore ================================================ target/ exercises/progress.db ================================================ FILE: .wr.toml ================================================ ================================================ FILE: Cargo.toml ================================================ [workspace] members = [ "exercises/*/*", "helpers/common", "helpers/mdbook-exercise-linker", "helpers/mdbook-link-shortener", "helpers/ticket_fields", ] resolver = "2" # This is needed to guarantee the expected behaviour on that specific exercise, # regardless of the "global" setting for `overflow-checks` on the `dev` profile. [profile.dev.package.copy] overflow-checks = true ================================================ FILE: README.md ================================================ # Learn Rust, one exercise at a time You've heard about Rust, but you never had the chance to try it out?\ This course is for you! You'll learn Rust by solving 100 exercises.\ You'll go from knowing nothing about Rust to being able to start writing your own programs, one exercise at a time. > [!NOTE] > This course has been written by [Mainmatter](https://mainmatter.com/rust-consulting/).\ > It's one of the trainings in [our portfolio of Rust workshops](https://mainmatter.com/services/workshops/rust/).\ > Check out our [landing page](https://mainmatter.com/rust-consulting/) if you're looking for Rust consulting or > training! ## Getting started Go to [rust-exercises.com](https://rust-exercises.com) and follow the instructions there to get started with the course. ## Requirements - **Rust** (follow instructions [here](https://www.rust-lang.org/tools/install)).\ If `rustup` is already installed on your system, run `rustup update` (or another appropriate command depending on how you installed Rust on your system) to make sure you're running on the latest stable version. - _(Optional but recommended)_ An IDE with Rust autocompletion support. We recommend one of the following: - [RustRover](https://www.jetbrains.com/rust/); - [Visual Studio Code](https://code.visualstudio.com) with the [`rust-analyzer`](https://marketplace.visualstudio.com/items?itemName=matklad.rust-analyzer) extension. ## Solutions You can find the solutions to the exercises in the [`solutions` branch](https://github.com/mainmatter/100-exercises-to-learn-rust/tree/solutions) of this repository. # License Copyright © 2024- Mainmatter GmbH (https://mainmatter.com), released under the [Creative Commons Attribution-NonCommercial 4.0 International license](https://creativecommons.org/licenses/by-nc/4.0/). ================================================ FILE: dprint.json ================================================ { "markdown": { }, "toml": { }, "excludes": [], "plugins": [ "https://plugins.dprint.dev/markdown-0.17.0.wasm", "https://plugins.dprint.dev/toml-0.6.1.wasm" ] } ================================================ FILE: exercises/01_intro/00_welcome/Cargo.toml ================================================ [package] name = "welcome_00" version = "0.1.0" edition = "2021" [lints.rust] # We silence dead code warnings for the time being in order to reduce # compiler noise. # We'll re-enable them again once we explain how visibility works in Rust. dead_code = "allow" ================================================ FILE: exercises/01_intro/00_welcome/src/lib.rs ================================================ // This is a Rust file. It is a plain text file with a `.rs` extension. // // Like most modern programming languages, Rust supports comments. You're looking at one right now! // Comments are ignored by the compiler; you can leverage them to annotate code with notes and // explanations. // There are various ways to write comments in Rust, each with its own purpose. // For now we'll stick to the most common one: the line comment. // Everything from `//` to the end of the line is considered a comment. // Exercises will include `TODO`, `todo!()` or `__` markers to draw your attention to the lines // where you need to write code. // You'll need to replace these markers with your own code to complete the exercise. // Sometimes it'll be enough to write a single line of code, other times you'll have to write // longer sections. // // If you get stuck for more than 10 minutes on an exercise, grab a trainer! We're here to help! // You can also find solutions to all exercises in the `solutions` git branch. fn greeting() -> &'static str { // TODO: fix me 👇 "I'm ready to __!" } // Your solutions will be automatically verified by a set of tests. // You can run these tests directly by invoking the `cargo test` command in your terminal, // from the root of this exercise's directory. That's what the `wr` command does for you // under the hood. // // Rust lets you write tests alongside your code. // The `#[cfg(test)]` attribute tells the compiler to only compile the code below when // running tests (i.e. when you run `cargo test`). // You'll learn more about attributes and testing later in the course. // For now, just know that you need to look for the `#[cfg(test)]` attribute to find the tests // that will be verifying the correctness of your solutions! // // ⚠️ **DO NOT MODIFY THE TESTS** ⚠️ // They are there to help you validate your solutions. You should only change the code that's being // tested, not the tests themselves. #[cfg(test)] mod tests { use crate::greeting; #[test] fn test_welcome() { assert_eq!(greeting(), "I'm ready to learn Rust!"); } } ================================================ FILE: exercises/01_intro/01_syntax/Cargo.toml ================================================ [package] name = "syntax" version = "0.1.0" edition = "2021" [lints.rust] # We silence dead code warnings for the time being in order to reduce # compiler noise. # We'll re-enable them again once we explain how visibility works in Rust. dead_code = "allow" ================================================ FILE: exercises/01_intro/01_syntax/src/lib.rs ================================================ // TODO: fix the function signature below to make the tests pass. // Make sure to read the compiler error message—the Rust compiler is your pair programming // partner in this course and it'll often guide you in the right direction! // // The input parameters should have the same type of the return type. fn compute(a, b) -> u32 { // Don't touch the function body. a + b * 2 } #[cfg(test)] mod tests { use crate::compute; #[test] fn case() { assert_eq!(compute(1, 2), 5); } } ================================================ FILE: exercises/02_basic_calculator/00_intro/Cargo.toml ================================================ [package] name = "intro_01" version = "0.1.0" edition = "2021" [lints.rust] # We silence dead code warnings for the time being in order to reduce # compiler noise. # We'll re-enable them again once we explain how visibility works in Rust. dead_code = "allow" ================================================ FILE: exercises/02_basic_calculator/00_intro/src/lib.rs ================================================ fn intro() -> &'static str { // TODO: fix me 👇 "I'm ready to __!" } #[cfg(test)] mod tests { use crate::intro; #[test] fn test_intro() { assert_eq!(intro(), "I'm ready to build a calculator in Rust!"); } } ================================================ FILE: exercises/02_basic_calculator/01_integers/Cargo.toml ================================================ [package] name = "integers" version = "0.1.0" edition = "2021" [lints.rust] # We silence dead code warnings for the time being in order to reduce # compiler noise. # We'll re-enable them again once we explain how visibility works in Rust. dead_code = "allow" ================================================ FILE: exercises/02_basic_calculator/01_integers/src/lib.rs ================================================ fn compute(a: u32, b: u32) -> u32 { // TODO: change the line below to fix the compiler error and make the tests pass. let multiplier: u8 = 4; a + b * multiplier } #[cfg(test)] mod tests { use crate::compute; #[test] fn case() { assert_eq!(compute(1, 2), 9); } } ================================================ FILE: exercises/02_basic_calculator/02_variables/Cargo.toml ================================================ [package] name = "variables" version = "0.1.0" edition = "2021" [lints.rust] # We silence dead code warnings for the time being in order to reduce # compiler noise. # We'll re-enable them again once we explain how visibility works in Rust. dead_code = "allow" ================================================ FILE: exercises/02_basic_calculator/02_variables/src/lib.rs ================================================ // 👇 The lines below, starting with `///`, are called **documentation comments**. // They attach documentation to the item that follows them. In this case, the `speed` function. // If you run `cargo doc --open` from this exercise's directory, Rust will generate // HTML documentation from these comments and open it in your browser. /// Given the start and end points of a journey, and the time it took to complete it, /// calculate the average speed. pub fn speed(start: u32, end: u32, time_elapsed: u32) -> u32 { // TODO: define a variable named `distance` with the right value to get tests to pass // Do you need to annotate the type of `distance`? Why or why not? // Don't change the line below distance / time_elapsed } #[cfg(test)] mod tests { use crate::speed; #[test] fn case1() { assert_eq!(speed(0, 10, 10), 1); } #[test] fn case2() { assert_eq!(speed(10, 30, 10), 2); } #[test] fn case3() { assert_eq!(speed(10, 31, 10), 2); } } ================================================ FILE: exercises/02_basic_calculator/03_if_else/Cargo.toml ================================================ [package] name = "if_else" version = "0.1.0" edition = "2021" [lints.rust] # We silence dead code warnings for the time being in order to reduce # compiler noise. # We'll re-enable them again once we explain how visibility works in Rust. dead_code = "allow" ================================================ FILE: exercises/02_basic_calculator/03_if_else/src/lib.rs ================================================ /// Return `12` if `n` is even, /// `13` if `n` is divisible by `3`, /// `17` otherwise. fn magic_number(n: u32) -> u32 { todo!() } #[cfg(test)] mod tests { use crate::magic_number; #[test] fn one() { assert_eq!(magic_number(1), 17); } #[test] fn two() { assert_eq!(magic_number(2), 12); } #[test] fn six() { assert_eq!(magic_number(6), 12); } #[test] fn nine() { assert_eq!(magic_number(9), 13); } #[test] fn high() { assert_eq!(magic_number(233), 17); } } ================================================ FILE: exercises/02_basic_calculator/04_panics/Cargo.toml ================================================ [package] name = "panics" version = "0.1.0" edition = "2021" [lints.rust] # We silence dead code warnings for the time being in order to reduce # compiler noise. # We'll re-enable them again once we explain how visibility works in Rust. dead_code = "allow" ================================================ FILE: exercises/02_basic_calculator/04_panics/src/lib.rs ================================================ /// Given the start and end points of a journey, and the time it took to complete the journey, /// calculate the average speed of the journey. fn speed(start: u32, end: u32, time_elapsed: u32) -> u32 { // TODO: Panic with a custom message if `time_elapsed` is 0 (end - start) / time_elapsed } #[cfg(test)] mod tests { use crate::speed; #[test] fn case1() { assert_eq!(speed(0, 10, 10), 1); } #[test] // 👇 With the `#[should_panic]` annotation we can assert that we expect the code // under test to panic. We can also check the panic message by using `expected`. // This is all part of Rust's built-in test framework! #[should_panic(expected = "The journey took no time at all. That's impossible!")] fn by_zero() { speed(0, 10, 0); } } ================================================ FILE: exercises/02_basic_calculator/05_factorial/Cargo.toml ================================================ [package] name = "factorial" version = "0.1.0" edition = "2021" [lints.rust] # We silence dead code warnings for the time being in order to reduce # compiler noise. # We'll re-enable them again once we explain how visibility works in Rust. dead_code = "allow" ================================================ FILE: exercises/02_basic_calculator/05_factorial/src/lib.rs ================================================ // Define a function named `factorial` that, given a non-negative integer `n`, // returns `n!`, the factorial of `n`. // // The factorial of `n` is defined as the product of all positive integers up to `n`. // For example, `5!` (read "five factorial") is `5 * 4 * 3 * 2 * 1`, which is `120`. // `0!` is defined to be `1`. // // We expect `factorial(0)` to return `1`, `factorial(1)` to return `1`, // `factorial(2)` to return `2`, and so on. // // Use only what you learned! No loops yet, so you'll have to use recursion! #[cfg(test)] mod tests { use crate::factorial; #[test] fn first() { assert_eq!(factorial(0), 1); } #[test] fn second() { assert_eq!(factorial(1), 1); } #[test] fn third() { assert_eq!(factorial(2), 2); } #[test] fn fifth() { assert_eq!(factorial(5), 120); } } ================================================ FILE: exercises/02_basic_calculator/06_while/Cargo.toml ================================================ [package] name = "while_" version = "0.1.0" edition = "2021" [lints.rust] # We silence dead code warnings for the time being in order to reduce # compiler noise. # We'll re-enable them again once we explain how visibility works in Rust. dead_code = "allow" ================================================ FILE: exercises/02_basic_calculator/06_while/src/lib.rs ================================================ // Rewrite the factorial function using a `while` loop. pub fn factorial(n: u32) -> u32 { // The `todo!()` macro is a placeholder that the compiler // interprets as "I'll get back to this later", thus // suppressing type errors. // It panics at runtime. todo!() } #[cfg(test)] mod tests { use crate::factorial; #[test] fn first() { assert_eq!(factorial(0), 1); } #[test] fn second() { assert_eq!(factorial(1), 1); } #[test] fn third() { assert_eq!(factorial(2), 2); } #[test] fn fifth() { assert_eq!(factorial(5), 120); } } ================================================ FILE: exercises/02_basic_calculator/07_for/Cargo.toml ================================================ [package] name = "for_" version = "0.1.0" edition = "2021" [lints.rust] # We silence dead code warnings for the time being in order to reduce # compiler noise. # We'll re-enable them again once we explain how visibility works in Rust. dead_code = "allow" ================================================ FILE: exercises/02_basic_calculator/07_for/src/lib.rs ================================================ // Rewrite the factorial function using a `for` loop. pub fn factorial(n: u32) -> u32 { todo!() } #[cfg(test)] mod tests { use crate::factorial; #[test] fn first() { assert_eq!(factorial(0), 1); } #[test] fn second() { assert_eq!(factorial(1), 1); } #[test] fn third() { assert_eq!(factorial(2), 2); } #[test] fn fifth() { assert_eq!(factorial(5), 120); } } ================================================ FILE: exercises/02_basic_calculator/08_overflow/Cargo.toml ================================================ [package] name = "overflow" version = "0.1.0" edition = "2021" [lints.rust] # We silence dead code warnings for the time being in order to reduce # compiler noise. # We'll re-enable them again once we explain how visibility works in Rust. dead_code = "allow" ================================================ FILE: exercises/02_basic_calculator/08_overflow/src/lib.rs ================================================ // Customize the `dev` profile to wrap around on overflow. // Check Cargo's documentation to find out the right syntax: // https://doc.rust-lang.org/cargo/reference/profiles.html // // For reasons that we'll explain later, the customization needs to be done in the `Cargo.toml` // at the root of the repository, not in the `Cargo.toml` of the exercise. pub fn factorial(n: u32) -> u32 { let mut result = 1; for i in 1..=n { result *= i; } result } #[cfg(test)] mod tests { use crate::factorial; #[test] fn twentieth() { // 20! is 2432902008176640000, which is too large to fit in a u32 // With the default dev profile, this will panic when you run `cargo test` // We want it to wrap around instead assert_eq!(factorial(20), 2_192_834_560); // ☝️ // A large number literal using underscores to improve readability! } #[test] fn first() { assert_eq!(factorial(0), 1); } #[test] fn second() { assert_eq!(factorial(1), 1); } #[test] fn third() { assert_eq!(factorial(2), 2); } #[test] fn fifth() { assert_eq!(factorial(5), 120); } } ================================================ FILE: exercises/02_basic_calculator/09_saturating/Cargo.toml ================================================ [package] name = "saturating" version = "0.1.0" edition = "2021" [lints.rust] # We silence dead code warnings for the time being in order to reduce # compiler noise. # We'll re-enable them again once we explain how visibility works in Rust. dead_code = "allow" ================================================ FILE: exercises/02_basic_calculator/09_saturating/src/lib.rs ================================================ pub fn factorial(n: u32) -> u32 { let mut result = 1; for i in 1..=n { // Use saturating multiplication to stop at the maximum value of u32 // rather than overflowing and wrapping around result *= i; } result } #[cfg(test)] mod tests { use crate::factorial; #[test] fn twentieth() { assert_eq!(factorial(20), u32::MAX); } #[test] fn first() { assert_eq!(factorial(0), 1); } #[test] fn second() { assert_eq!(factorial(1), 1); } #[test] fn third() { assert_eq!(factorial(2), 2); } #[test] fn fifth() { assert_eq!(factorial(5), 120); } } ================================================ FILE: exercises/02_basic_calculator/10_as_casting/Cargo.toml ================================================ [package] name = "as_cast" version = "0.1.0" edition = "2021" [lints.rust] # We silence dead code warnings for the time being in order to reduce # compiler noise. # We'll re-enable them again once we explain how visibility works in Rust. dead_code = "allow" ================================================ FILE: exercises/02_basic_calculator/10_as_casting/src/lib.rs ================================================ // TODO: based on what you learned in this section, replace `todo!()` with // the correct value after the conversion. #[cfg(test)] mod tests { #[test] fn u16_to_u32() { let v: u32 = todo!(); assert_eq!(47u16 as u32, v); } #[test] fn u8_to_i8() { // The compiler is smart enough to know that the value 255 cannot fit // inside an i8, so it'll emit a hard error. We intentionally disable // this guardrail to make this (bad) conversion possible. // The compiler is only able to pick on this because the value is a // literal. If we were to use a variable, the compiler wouldn't be able to // catch this at compile time. #[allow(overflowing_literals)] let x = { 255 as i8 }; // You could solve this by using exactly the same expression as above, // but that would defeat the purpose of the exercise. Instead, use a genuine // `i8` value that is equivalent to `255` when converted to `u8`. let y: i8 = todo!(); assert_eq!(x, y); } #[test] fn bool_to_u8() { let v: u8 = todo!(); assert_eq!(true as u8, v); } } ================================================ FILE: exercises/03_ticket_v1/00_intro/Cargo.toml ================================================ [package] name = "intro_02" version = "0.1.0" edition = "2021" [lints.rust] # We silence dead code warnings for the time being in order to reduce # compiler noise. # We'll re-enable them again once we explain how visibility works in Rust. dead_code = "allow" ================================================ FILE: exercises/03_ticket_v1/00_intro/src/lib.rs ================================================ fn intro() -> &'static str { // TODO: fix me 👇 "I'm ready to __!" } #[cfg(test)] mod tests { use crate::intro; #[test] fn test_intro() { assert_eq!(intro(), "I'm ready to start modelling a software ticket!"); } } ================================================ FILE: exercises/03_ticket_v1/01_struct/Cargo.toml ================================================ [package] name = "struct_" version = "0.1.0" edition = "2021" [lints.rust] # We silence dead code warnings for the time being in order to reduce # compiler noise. # We'll re-enable them again once we explain how visibility works in Rust. dead_code = "allow" ================================================ FILE: exercises/03_ticket_v1/01_struct/src/lib.rs ================================================ // Define a struct named `Order` with the following fields: // - `price`, an unsigned integer // - `quantity`, an unsigned integer // // It should also have a method named `is_available` that returns a `true` if the quantity is // greater than 0, otherwise `false`. #[cfg(test)] mod tests { use super::*; #[test] fn test_order_is_available() { let order = Order { price: 100, quantity: 10, }; assert!(order.is_available()); } #[test] fn test_order_is_not_available() { let order = Order { price: 100, quantity: 0, }; assert!(!order.is_available()); } } ================================================ FILE: exercises/03_ticket_v1/02_validation/Cargo.toml ================================================ [package] name = "validation" version = "0.1.0" edition = "2021" [dev-dependencies] common = { path = "../../../helpers/common" } [lints.rust] # We silence dead code warnings for the time being in order to reduce # compiler noise. # We'll re-enable them again once we explain how visibility works in Rust. dead_code = "allow" ================================================ FILE: exercises/03_ticket_v1/02_validation/src/lib.rs ================================================ struct Ticket { title: String, description: String, status: String, } impl Ticket { // TODO: implement the `new` function. // The following requirements should be met: // - Only `To-Do`, `In Progress`, and `Done` statuses are allowed. // - The `title` and `description` fields should not be empty. // - the `title` should be at most 50 bytes long. // - the `description` should be at most 500 bytes long. // The method should panic if any of the requirements are not met. // You can find the needed panic messages in the tests. // // You'll have to use what you learned in the previous exercises, // as well as some `String` methods. Use the documentation of Rust's standard library // to find the most appropriate options -> https://doc.rust-lang.org/std/string/struct.String.html fn new(title: String, description: String, status: String) -> Self { todo!(); Self { title, description, status, } } } #[cfg(test)] mod tests { use super::*; use common::{overly_long_description, overly_long_title, valid_description, valid_title}; #[test] #[should_panic(expected = "Title cannot be empty")] fn title_cannot_be_empty() { Ticket::new("".into(), valid_description(), "To-Do".into()); } #[test] #[should_panic(expected = "Description cannot be empty")] fn description_cannot_be_empty() { Ticket::new(valid_title(), "".into(), "To-Do".into()); } #[test] #[should_panic(expected = "Title cannot be longer than 50 bytes")] fn title_cannot_be_longer_than_fifty_chars() { Ticket::new(overly_long_title(), valid_description(), "To-Do".into()); } #[test] #[should_panic(expected = "Description cannot be longer than 500 bytes")] fn description_cannot_be_longer_than_500_chars() { Ticket::new(valid_title(), overly_long_description(), "To-Do".into()); } #[test] #[should_panic(expected = "Only `To-Do`, `In Progress`, and `Done` statuses are allowed")] fn status_must_be_valid() { Ticket::new(valid_title(), valid_description(), "Funny".into()); } #[test] fn done_is_allowed() { Ticket::new(valid_title(), valid_description(), "Done".into()); } #[test] fn in_progress_is_allowed() { Ticket::new(valid_title(), valid_description(), "In Progress".into()); } } ================================================ FILE: exercises/03_ticket_v1/03_modules/Cargo.toml ================================================ [package] name = "modules" version = "0.1.0" edition = "2021" [lints.rust] # We silence dead code warnings for the time being in order to reduce # compiler noise. # We'll re-enable them again once we explain how visibility works in Rust. dead_code = "allow" ================================================ FILE: exercises/03_ticket_v1/03_modules/src/lib.rs ================================================ mod helpers { // TODO: Make this code compile, either by adding a `use` statement or by using // the appropriate path to refer to the `Ticket` struct. fn create_todo_ticket(title: String, description: String) -> Ticket { Ticket::new(title, description, "To-Do".into()) } } struct Ticket { title: String, description: String, status: String, } impl Ticket { fn new(title: String, description: String, status: String) -> Ticket { if title.is_empty() { panic!("Title cannot be empty"); } if title.len() > 50 { panic!("Title cannot be longer than 50 bytes"); } if description.is_empty() { panic!("Description cannot be empty"); } if description.len() > 500 { panic!("Description cannot be longer than 500 bytes"); } if status != "To-Do" && status != "In Progress" && status != "Done" { panic!("Only `To-Do`, `In Progress`, and `Done` statuses are allowed"); } Ticket { title, description, status, } } } ================================================ FILE: exercises/03_ticket_v1/04_visibility/Cargo.toml ================================================ [package] name = "visibility" version = "0.1.0" edition = "2021" ================================================ FILE: exercises/03_ticket_v1/04_visibility/src/lib.rs ================================================ mod ticket { struct Ticket { title: String, description: String, status: String, } impl Ticket { fn new(title: String, description: String, status: String) -> Ticket { if title.is_empty() { panic!("Title cannot be empty"); } if title.len() > 50 { panic!("Title cannot be longer than 50 bytes"); } if description.is_empty() { panic!("Description cannot be empty"); } if description.len() > 500 { panic!("Description cannot be longer than 500 bytes"); } if status != "To-Do" && status != "In Progress" && status != "Done" { panic!("Only `To-Do`, `In Progress`, and `Done` statuses are allowed"); } Ticket { title, description, status, } } } } // TODO: **Exceptionally**, you'll be modifying both the `ticket` module and the `tests` module // in this exercise. #[cfg(test)] mod tests { // TODO: Add the necessary `pub` modifiers in the parent module to remove the compiler // errors about the use statement below. use super::ticket::Ticket; // Be careful though! We don't want this function to compile after you have changed // visibility to make the use statement compile! // Once you have verified that it indeed doesn't compile, comment it out. fn should_not_be_possible() { let ticket = Ticket::new("A title".into(), "A description".into(), "To-Do".into()); // You should be seeing this error when trying to run this exercise: // // error[E0616]: field `description` of struct `Ticket` is private // | // | assert_eq!(ticket.description, "A description"); // | ^^^^^^^^^^^^^^^^^^ // // TODO: Once you have verified that the below does not compile, // comment the line out to move on to the next exercise! assert_eq!(ticket.description, "A description"); } fn encapsulation_cannot_be_violated() { // This should be impossible as well, with a similar error as the one encountered above. // (It will throw a compilation error only after you have commented the faulty line // in the previous test - next compilation stage!) // // This proves that `Ticket::new` is now the only way to get a `Ticket` instance. // It's impossible to create a ticket with an illegal title or description! // // TODO: Once you have verified that the below does not compile, // comment the lines out to move on to the next exercise! let ticket = Ticket { title: "A title".into(), description: "A description".into(), status: "To-Do".into(), }; } } ================================================ FILE: exercises/03_ticket_v1/05_encapsulation/Cargo.toml ================================================ [package] name = "encapsulation" version = "0.1.0" edition = "2021" ================================================ FILE: exercises/03_ticket_v1/05_encapsulation/src/lib.rs ================================================ pub mod ticket { pub struct Ticket { title: String, description: String, status: String, } impl Ticket { pub fn new(title: String, description: String, status: String) -> Ticket { if title.is_empty() { panic!("Title cannot be empty"); } if title.len() > 50 { panic!("Title cannot be longer than 50 bytes"); } if description.is_empty() { panic!("Description cannot be empty"); } if description.len() > 500 { panic!("Description cannot be longer than 500 bytes"); } if status != "To-Do" && status != "In Progress" && status != "Done" { panic!("Only `To-Do`, `In Progress`, and `Done` statuses are allowed"); } Ticket { title, description, status, } } // TODO: Add three public methods to the `Ticket` struct: // - `title` that returns the `title` field. // - `description` that returns the `description` field. // - `status` that returns the `status` field. } } #[cfg(test)] mod tests { use super::ticket::Ticket; #[test] fn description() { let ticket = Ticket::new("A title".into(), "A description".into(), "To-Do".into()); assert_eq!(ticket.description(), "A description"); } #[test] fn title() { let ticket = Ticket::new("A title".into(), "A description".into(), "To-Do".into()); assert_eq!(ticket.title(), "A title"); } #[test] fn status() { let ticket = Ticket::new("A title".into(), "A description".into(), "To-Do".into()); assert_eq!(ticket.status(), "To-Do"); } } ================================================ FILE: exercises/03_ticket_v1/06_ownership/Cargo.toml ================================================ [package] name = "ownership" version = "0.1.0" edition = "2021" ================================================ FILE: exercises/03_ticket_v1/06_ownership/src/lib.rs ================================================ // TODO: based on what we just learned about ownership, it sounds like immutable references // are a good fit for our accessor methods. // Change the existing implementation of `Ticket`'s accessor methods to take a reference // to `self` as an argument, rather than taking ownership of it. pub struct Ticket { title: String, description: String, status: String, } impl Ticket { pub fn new(title: String, description: String, status: String) -> Ticket { if title.is_empty() { panic!("Title cannot be empty"); } if title.len() > 50 { panic!("Title cannot be longer than 50 bytes"); } if description.is_empty() { panic!("Description cannot be empty"); } if description.len() > 500 { panic!("Description cannot be longer than 500 bytes"); } if status != "To-Do" && status != "In Progress" && status != "Done" { panic!("Only `To-Do`, `In Progress`, and `Done` statuses are allowed"); } Ticket { title, description, status, } } pub fn title(self) -> String { self.title } pub fn description(self) -> String { self.description } pub fn status(self) -> String { self.status } } #[cfg(test)] mod tests { use super::Ticket; #[test] fn works() { let ticket = Ticket::new("A title".into(), "A description".into(), "To-Do".into()); // If you change the signatures as requested, this should compile: // we can call these methods one after the other because they borrow `self` // rather than taking ownership of it. assert_eq!(ticket.title(), "A title"); assert_eq!(ticket.description(), "A description"); assert_eq!(ticket.status(), "To-Do"); } } ================================================ FILE: exercises/03_ticket_v1/07_setters/Cargo.toml ================================================ [package] name = "setters" version = "0.1.0" edition = "2021" [dev-dependencies] common = { path = "../../../helpers/common" } ================================================ FILE: exercises/03_ticket_v1/07_setters/src/lib.rs ================================================ // TODO: Add &mut-setters to the `Ticket` struct for each of its fields. // Make sure to enforce the same validation rules you have in `Ticket::new`! // Even better, extract that logic and reuse it in both places. You can use // private functions or private static methods for that. pub struct Ticket { title: String, description: String, status: String, } impl Ticket { pub fn new(title: String, description: String, status: String) -> Ticket { if title.is_empty() { panic!("Title cannot be empty"); } if title.len() > 50 { panic!("Title cannot be longer than 50 bytes"); } if description.is_empty() { panic!("Description cannot be empty"); } if description.len() > 500 { panic!("Description cannot be longer than 500 bytes"); } if status != "To-Do" && status != "In Progress" && status != "Done" { panic!("Only `To-Do`, `In Progress`, and `Done` statuses are allowed"); } Ticket { title, description, status, } } pub fn title(&self) -> &String { &self.title } pub fn description(&self) -> &String { &self.description } pub fn status(&self) -> &String { &self.status } } #[cfg(test)] mod tests { use super::Ticket; use common::{overly_long_description, overly_long_title, valid_description, valid_title}; #[test] fn works() { let mut ticket = Ticket::new("A title".into(), "A description".into(), "To-Do".into()); ticket.set_title("A new title".into()); ticket.set_description("A new description".into()); ticket.set_status("Done".into()); assert_eq!(ticket.title(), "A new title"); assert_eq!(ticket.description(), "A new description"); assert_eq!(ticket.status(), "Done"); } #[test] #[should_panic(expected = "Title cannot be empty")] fn title_cannot_be_empty() { Ticket::new(valid_title(), valid_description(), "To-Do".into()).set_title("".into()); } #[test] #[should_panic(expected = "Description cannot be empty")] fn description_cannot_be_empty() { Ticket::new(valid_title(), valid_description(), "To-Do".into()).set_description("".into()); } #[test] #[should_panic(expected = "Title cannot be longer than 50 bytes")] fn title_cannot_be_longer_than_fifty_chars() { Ticket::new(valid_title(), valid_description(), "To-Do".into()) .set_title(overly_long_title()) } #[test] #[should_panic(expected = "Description cannot be longer than 500 bytes")] fn description_cannot_be_longer_than_500_chars() { Ticket::new(valid_title(), valid_description(), "To-Do".into()) .set_description(overly_long_description()) } #[test] #[should_panic(expected = "Only `To-Do`, `In Progress`, and `Done` statuses are allowed")] fn status_must_be_valid() { Ticket::new(valid_title(), valid_description(), "To-Do".into()).set_status("Funny".into()); } } ================================================ FILE: exercises/03_ticket_v1/08_stack/Cargo.toml ================================================ [package] name = "stack" version = "0.1.0" edition = "2021" ================================================ FILE: exercises/03_ticket_v1/08_stack/src/lib.rs ================================================ // TODO: based on what you learned in this section, replace `todo!()` with // the correct **stack size** for the respective type. #[cfg(test)] mod tests { use std::mem::size_of; #[test] fn u16_size() { assert_eq!(size_of::(), todo!()); } #[test] fn i32_size() { assert_eq!(size_of::(), todo!()); } #[test] fn bool_size() { assert_eq!(size_of::(), todo!()); } } ================================================ FILE: exercises/03_ticket_v1/09_heap/Cargo.toml ================================================ [package] name = "heap" version = "0.1.0" edition = "2021" ================================================ FILE: exercises/03_ticket_v1/09_heap/src/lib.rs ================================================ pub struct Ticket { title: String, description: String, status: String, } // TODO: based on what you learned in this section, replace `todo!()` with // the correct **stack size** for the respective type. #[cfg(test)] mod tests { use super::Ticket; use std::mem::size_of; #[test] fn string_size() { assert_eq!(size_of::(), todo!()); } #[test] fn ticket_size() { // This is a tricky question! // The "intuitive" answer happens to be the correct answer this time, // but, in general, the memory layout of structs is a more complex topic. // If you're curious, check out the "Type layout" section of The Rust Reference // https://doc.rust-lang.org/reference/type-layout.html for more information. assert_eq!(size_of::(), todo!()); } } ================================================ FILE: exercises/03_ticket_v1/10_references_in_memory/Cargo.toml ================================================ [package] name = "references_in_memory" version = "0.1.0" edition = "2021" ================================================ FILE: exercises/03_ticket_v1/10_references_in_memory/src/lib.rs ================================================ pub struct Ticket { title: String, description: String, status: String, } // TODO: based on what you learned in this section, replace `todo!()` with // the correct **stack size** for the respective type. #[cfg(test)] mod tests { use super::Ticket; use std::mem::size_of; #[test] fn u16_ref_size() { assert_eq!(size_of::<&u16>(), todo!()); } #[test] fn u64_mut_ref_size() { assert_eq!(size_of::<&mut u64>(), todo!()); } #[test] fn ticket_ref_size() { assert_eq!(size_of::<&Ticket>(), todo!()); } } ================================================ FILE: exercises/03_ticket_v1/11_destructor/Cargo.toml ================================================ [package] name = "destructor" version = "0.1.0" edition = "2021" ================================================ FILE: exercises/03_ticket_v1/11_destructor/src/lib.rs ================================================ // We need some more machinery to write a proper exercise for destructors. // We'll pick the concept up again in a later chapter after covering traits and // interior mutability. fn outro() -> &'static str { "I have a basic understanding of __!" } #[cfg(test)] mod tests { use crate::outro; #[test] fn test_outro() { assert_eq!(outro(), "I have a basic understanding of destructors!"); } } ================================================ FILE: exercises/03_ticket_v1/12_outro/Cargo.toml ================================================ [package] name = "outro_02" version = "0.1.0" edition = "2021" ================================================ FILE: exercises/03_ticket_v1/12_outro/src/lib.rs ================================================ // TODO: Define a new `Order` type. // It should keep track of three pieces of information: `product_name`, `quantity`, and `unit_price`. // The product name can't be empty and it can't be longer than 300 bytes. // The quantity must be strictly greater than zero. // The unit price is in cents and must be strictly greater than zero. // Order must include a method named `total` that returns the total price of the order. // Order must provide setters and getters for each field. // // Tests are located in a different place this time—in the `tests` folder. // The `tests` folder is a special location for `cargo`. It's where it looks for **integration tests**. // Integration here has a very specific meaning: they test **the public API** of your project. // You'll need to pay attention to the visibility of your types and methods; integration // tests can't access private or `pub(crate)` items. ================================================ FILE: exercises/03_ticket_v1/12_outro/tests/integration.rs ================================================ use outro_02::Order; // Files inside the `tests` directory are only compiled when you run tests. // As a consequence, we don't need the `#[cfg(test)]` attribute for conditional compilation—it's // implied. #[test] fn test_order() { let mut order = Order::new("Rusty Book".to_string(), 3, 2999); assert_eq!(order.product_name(), "Rusty Book"); assert_eq!(order.quantity(), &3); assert_eq!(order.unit_price(), &2999); assert_eq!(order.total(), 8997); order.set_product_name("Rust Book".to_string()); order.set_quantity(2); order.set_unit_price(3999); assert_eq!(order.product_name(), "Rust Book"); assert_eq!(order.quantity(), &2); assert_eq!(order.unit_price(), &3999); assert_eq!(order.total(), 7998); } // Validation tests #[test] #[should_panic] fn test_empty_product_name() { Order::new("".to_string(), 3, 2999); } #[test] #[should_panic] fn test_long_product_name() { Order::new("a".repeat(301), 3, 2999); } #[test] #[should_panic] fn test_zero_quantity() { Order::new("Rust Book".to_string(), 0, 2999); } #[test] #[should_panic] fn test_zero_unit_price() { Order::new("Rust Book".to_string(), 3, 0); } ================================================ FILE: exercises/04_traits/00_intro/Cargo.toml ================================================ [package] name = "intro_03" version = "0.1.0" edition = "2021" ================================================ FILE: exercises/04_traits/00_intro/src/lib.rs ================================================ fn intro() -> &'static str { // TODO: fix me 👇 "I'm ready to __!" } #[cfg(test)] mod tests { use crate::intro; #[test] fn test_intro() { assert_eq!(intro(), "I'm ready to learn about traits!"); } } ================================================ FILE: exercises/04_traits/01_trait/Cargo.toml ================================================ [package] name = "trait_" version = "0.1.0" edition = "2021" ================================================ FILE: exercises/04_traits/01_trait/src/lib.rs ================================================ // Define a trait named `IsEven` that has a method `is_even` that returns a `true` if `self` is // even, otherwise `false`. // // Then implement the trait for `u32` and `i32`. #[cfg(test)] mod tests { use super::*; #[test] fn test_u32_is_even() { assert!(42u32.is_even()); assert!(!43u32.is_even()); } #[test] fn test_i32_is_even() { assert!(42i32.is_even()); assert!(!43i32.is_even()); assert!(0i32.is_even()); assert!(!(-1i32).is_even()); } } ================================================ FILE: exercises/04_traits/02_orphan_rule/Cargo.toml ================================================ [package] name = "orphan" version = "0.1.0" edition = "2021" ================================================ FILE: exercises/04_traits/02_orphan_rule/src/lib.rs ================================================ // TODO: this is an example of an orphan rule violation. // We're implementing a foreign trait (`PartialEq`, from `std`) on // a foreign type (`u32`, from `std`). // Look at the compiler error to get familiar with what it looks like. // Then delete the code below and move on to the next exercise. impl PartialEq for u32 { fn eq(&self, _other: &Self) -> bool { todo!() } } ================================================ FILE: exercises/04_traits/03_operator_overloading/Cargo.toml ================================================ [package] name = "overloading" version = "0.1.0" edition = "2021" ================================================ FILE: exercises/04_traits/03_operator_overloading/src/lib.rs ================================================ use std::cmp::PartialEq; struct Ticket { title: String, description: String, status: String, } // TODO: Implement the `PartialEq` trait for `Ticket`. impl PartialEq for Ticket {} #[cfg(test)] mod tests { use super::*; #[test] fn test_partial_eq() { let title = "title"; let description = "description"; let status = "To-Do"; let ticket1 = Ticket { title: title.to_string(), description: description.to_string(), status: status.to_string(), }; let ticket2 = Ticket { title: title.to_string(), description: description.to_string(), status: status.to_string(), }; assert!(ticket1 == ticket2); } #[test] fn test_description_not_matching() { let title = "title"; let status = "To-Do"; let ticket1 = Ticket { title: title.to_string(), description: "description".to_string(), status: status.to_string(), }; let ticket2 = Ticket { title: title.to_string(), description: "description2".to_string(), status: status.to_string(), }; assert!(ticket1 != ticket2); } #[test] fn test_title_not_matching() { let status = "To-Do"; let description = "description"; let ticket1 = Ticket { title: "title".to_string(), description: description.to_string(), status: status.to_string(), }; let ticket2 = Ticket { title: "title2".to_string(), description: description.to_string(), status: status.to_string(), }; assert!(ticket1 != ticket2); } #[test] fn test_status_not_matching() { let title = "title"; let description = "description"; let ticket1 = Ticket { title: title.to_string(), description: description.to_string(), status: "status".to_string(), }; let ticket2 = Ticket { title: title.to_string(), description: description.to_string(), status: "status2".to_string(), }; assert!(ticket1 != ticket2); } } ================================================ FILE: exercises/04_traits/04_derive/Cargo.toml ================================================ [package] name = "derives" version = "0.1.0" edition = "2021" ================================================ FILE: exercises/04_traits/04_derive/src/lib.rs ================================================ // TODO: A (derivable) trait implementation is missing for this exercise to compile successfully. // Fix it! // // # `Debug` primer // // `Debug` returns a representation of a Rust type that's suitable for debugging (hence the name). // `assert_eq!` requires `Ticket` to implement `Debug` because, when the assertion fails, it tries to // print both sides of the comparison to the terminal. // If the compared type doesn't implement `Debug`, it doesn't know how to represent them! #[derive(PartialEq)] struct Ticket { title: String, description: String, status: String, } #[cfg(test)] mod tests { use super::*; #[test] fn test_partial_eq() { let title = "title"; let description = "description"; let status = "To-Do"; let ticket1 = Ticket { title: title.to_string(), description: description.to_string(), status: status.to_string(), }; let ticket2 = Ticket { title: title.to_string(), description: description.to_string(), status: status.to_string(), }; assert_eq!(ticket1, ticket2); } #[test] fn test_description_not_matching() { let title = "title"; let status = "To-Do"; let ticket1 = Ticket { title: title.to_string(), description: "description".to_string(), status: status.to_string(), }; let ticket2 = Ticket { title: title.to_string(), description: "description2".to_string(), status: status.to_string(), }; assert_ne!(ticket1, ticket2); } #[test] fn test_title_not_matching() { let status = "To-Do"; let description = "description"; let ticket1 = Ticket { title: "title".to_string(), description: description.to_string(), status: status.to_string(), }; let ticket2 = Ticket { title: "title2".to_string(), description: description.to_string(), status: status.to_string(), }; assert_ne!(ticket1, ticket2); } #[test] fn test_status_not_matching() { let title = "title"; let description = "description"; let ticket1 = Ticket { title: title.to_string(), description: description.to_string(), status: "status".to_string(), }; let ticket2 = Ticket { title: title.to_string(), description: description.to_string(), status: "status2".to_string(), }; assert_ne!(ticket1, ticket2); } } ================================================ FILE: exercises/04_traits/05_trait_bounds/Cargo.toml ================================================ [package] name = "trait_bounds" version = "0.1.0" edition = "2021" ================================================ FILE: exercises/04_traits/05_trait_bounds/src/lib.rs ================================================ // TODO: Add the necessary trait bounds to `min` so that it compiles successfully. // Refer to the documentation of the `std::cmp` module for more information on the traits you might need. // // Note: there are different trait bounds that'll make the compiler happy, but they come with // different _semantics_. We'll cover those differences later in the course when we talk about ordered // collections (e.g. BTreeMap). /// Return the minimum of two values. pub fn min(left: T, right: T) -> T { if left <= right { left } else { right } } ================================================ FILE: exercises/04_traits/06_str_slice/Cargo.toml ================================================ [package] name = "str_slice" version = "0.1.0" edition = "2021" [dev-dependencies] common = { path = "../../../helpers/common" } ================================================ FILE: exercises/04_traits/06_str_slice/src/lib.rs ================================================ // TODO: Re-implement `Ticket`'s accessor methods. This time return a `&str` rather than a `&String`. pub struct Ticket { title: String, description: String, status: String, } impl Ticket { pub fn new(title: String, description: String, status: String) -> Ticket { if title.is_empty() { panic!("Title cannot be empty"); } if title.len() > 50 { panic!("Title cannot be longer than 50 bytes"); } if description.is_empty() { panic!("Description cannot be empty"); } if description.len() > 500 { panic!("Description cannot be longer than 500 bytes"); } if status != "To-Do" && status != "In Progress" && status != "Done" { panic!("Only `To-Do`, `In Progress`, and `Done` statuses are allowed"); } Ticket { title, description, status, } } pub fn title(&self) -> &String { &self.title } pub fn description(&self) -> &String { &self.description } pub fn status(&self) -> &String { &self.status } } #[cfg(test)] mod tests { use super::*; use common::{valid_description, valid_title}; use std::any::{Any, TypeId}; #[test] fn test_type() { let ticket = Ticket::new(valid_title(), valid_description(), "To-Do".to_string()); // Some dark magic to verify that you used the expected return types assert_eq!(TypeId::of::(), ticket.title().type_id()); assert_eq!(TypeId::of::(), ticket.description().type_id()); assert_eq!(TypeId::of::(), ticket.status().type_id()); } } ================================================ FILE: exercises/04_traits/07_deref/Cargo.toml ================================================ [package] name = "deref" version = "0.1.0" edition = "2021" ================================================ FILE: exercises/04_traits/07_deref/src/lib.rs ================================================ // TODO: whenever `title` and `description` are returned via their accessor methods, they // should be normalized—i.e. leading and trailing whitespace should be removed. // There is a method in Rust's standard library that can help with this, but you won't // find it in the documentation for `String`. // Can you figure out where it is defined and how to use it? pub struct Ticket { title: String, description: String, status: String, } impl Ticket { pub fn title(&self) -> &str { todo!() } pub fn description(&self) -> &str { todo!() } } #[cfg(test)] mod tests { use super::*; #[test] fn test_normalization() { let ticket = Ticket { title: " A title ".to_string(), description: " A description ".to_string(), status: "To-Do".to_string(), }; assert_eq!("A title", ticket.title()); assert_eq!("A description", ticket.description()); } } ================================================ FILE: exercises/04_traits/08_sized/Cargo.toml ================================================ [package] name = "sized" version = "0.1.0" edition = "2021" ================================================ FILE: exercises/04_traits/08_sized/src/lib.rs ================================================ pub fn example() { // Trying to get the size of a str (or any other DST) // via `std::mem::size_of` will result in a compile-time error. // // TODO: Comment out the following line and move on to the next exercise. std::mem::size_of::(); } ================================================ FILE: exercises/04_traits/09_from/Cargo.toml ================================================ [package] name = "from" version = "0.1.0" edition = "2021" ================================================ FILE: exercises/04_traits/09_from/src/lib.rs ================================================ // TODO: Implement the `From` trait for the `WrappingU32` type to make `example` compile. pub struct WrappingU32 { value: u32, } fn example() { let wrapping: WrappingU32 = 42.into(); let wrapping = WrappingU32::from(42); } ================================================ FILE: exercises/04_traits/10_assoc_vs_generic/Cargo.toml ================================================ [package] name = "assoc_vs_generic" version = "0.1.0" edition = "2021" ================================================ FILE: exercises/04_traits/10_assoc_vs_generic/src/lib.rs ================================================ // TODO: Define a new trait, `Power`, that has a method `power` that raises `self` // to the power of `n`. // The trait definition and its implementations should be enough to get // the tests to compile and pass. // // Recommendation: you may be tempted to write a generic implementation to handle // all cases at once. However, this is fairly complicated and requires the use of // additional crates (i.e. `num-traits`). // Even then, it might be preferable to use a simple macro instead to avoid // the complexity of a highly generic implementation. Check out the // "Little book of Rust macros" (https://veykril.github.io/tlborm/) if you're // interested in learning more about it. // You don't have to though: it's perfectly okay to write three separate // implementations manually. Venture further only if you're curious. #[cfg(test)] mod tests { use super::Power; #[test] fn test_power_u16() { let x: u32 = 2_u32.power(3u16); assert_eq!(x, 8); } #[test] fn test_power_u32() { let x: u32 = 2_u32.power(3u32); assert_eq!(x, 8); } #[test] fn test_power_ref_u32() { let x: u32 = 2_u32.power(&3u32); assert_eq!(x, 8); } } ================================================ FILE: exercises/04_traits/11_clone/Cargo.toml ================================================ [package] name = "clone" version = "0.1.0" edition = "2021" ================================================ FILE: exercises/04_traits/11_clone/src/lib.rs ================================================ // TODO: add the necessary `Clone` implementations (and invocations) // to get the code to compile. pub fn summary(ticket: Ticket) -> (Ticket, Summary) { (ticket, ticket.summary()) } pub struct Ticket { pub title: String, pub description: String, pub status: String, } impl Ticket { pub fn summary(self) -> Summary { Summary { title: self.title, status: self.status, } } } pub struct Summary { pub title: String, pub status: String, } ================================================ FILE: exercises/04_traits/12_copy/Cargo.toml ================================================ [package] name = "copy" version = "0.1.0" edition = "2021" ================================================ FILE: exercises/04_traits/12_copy/src/lib.rs ================================================ // TODO: implement the necessary traits to make the test compile and pass. // You *can't* modify the test. pub struct WrappingU32 { value: u32, } impl WrappingU32 { pub fn new(value: u32) -> Self { Self { value } } } #[cfg(test)] mod tests { use super::*; #[test] fn test_ops() { let x = WrappingU32::new(42); let y = WrappingU32::new(31); let z = WrappingU32::new(u32::MAX); assert_eq!(x + y + y + z, WrappingU32::new(103)); } } ================================================ FILE: exercises/04_traits/13_drop/Cargo.toml ================================================ [package] name = "drop" version = "0.1.0" edition = "2021" ================================================ FILE: exercises/04_traits/13_drop/src/lib.rs ================================================ // TODO: implement a so-called "Drop bomb": a type that panics when dropped // unless a certain operation has been performed on it. // You can see the expected API in the tests below. #[cfg(test)] mod tests { use super::*; #[test] #[should_panic] fn test_drop_bomb() { let bomb = DropBomb::new(); // The bomb should panic when dropped } #[test] fn test_defused_drop_bomb() { let mut bomb = DropBomb::new(); bomb.defuse(); // The bomb should not panic when dropped // since it has been defused } } ================================================ FILE: exercises/04_traits/14_outro/Cargo.toml ================================================ [package] name = "outro_03" version = "0.1.0" edition = "2021" ================================================ FILE: exercises/04_traits/14_outro/src/lib.rs ================================================ // TODO: Define a new `SaturatingU16` type. // It should hold a `u16` value. // It should provide conversions from `u16`, `u8`, `&u16` and `&u8`. // It should support addition with a right-hand side of type // SaturatingU16, u16, &u16, and &SaturatingU16. Addition should saturate at the // maximum value for `u16`. // It should be possible to compare it with another `SaturatingU16` or a `u16`. // It should be possible to print its debug representation. // // Tests are located in the `tests` folder—pay attention to the visibility of your types and methods. ================================================ FILE: exercises/04_traits/14_outro/tests/integration.rs ================================================ use outro_03::SaturatingU16; #[test] fn test_saturating_u16() { let a: SaturatingU16 = (&10u8).into(); let b: SaturatingU16 = 5u8.into(); let c: SaturatingU16 = u16::MAX.into(); let d: SaturatingU16 = (&1u16).into(); let e = &c; assert_eq!(a + b, SaturatingU16::from(15u16)); assert_eq!(a + c, SaturatingU16::from(u16::MAX)); assert_eq!(a + d, SaturatingU16::from(11u16)); assert_eq!(a + a, 20u16); assert_eq!(a + 5u16, 15u16); assert_eq!(a + e, SaturatingU16::from(u16::MAX)); } ================================================ FILE: exercises/05_ticket_v2/00_intro/Cargo.toml ================================================ [package] name = "intro_04" version = "0.1.0" edition = "2021" ================================================ FILE: exercises/05_ticket_v2/00_intro/src/lib.rs ================================================ fn intro() -> &'static str { // TODO: fix me 👇 "I'm ready to __!" } #[cfg(test)] mod tests { use crate::intro; #[test] fn test_intro() { assert_eq!(intro(), "I'm ready to refine the `Ticket` type!"); } } ================================================ FILE: exercises/05_ticket_v2/01_enum/Cargo.toml ================================================ [package] name = "enum_" version = "0.1.0" edition = "2021" [dev-dependencies] common = { path = "../../../helpers/common" } ================================================ FILE: exercises/05_ticket_v2/01_enum/src/lib.rs ================================================ // TODO: use `Status` as type for `Ticket::status` // Adjust the signature and implementation of all other methods as necessary. #[derive(Debug, PartialEq)] // `derive`s are recursive: it can only derive `PartialEq` if all fields also implement `PartialEq`. // Same holds for `Debug`. Do what you must with `Status` to make this work. struct Ticket { title: String, description: String, status: String, } enum Status { // TODO: add the missing variants } impl Ticket { pub fn new(title: String, description: String, status: String) -> Ticket { if title.is_empty() { panic!("Title cannot be empty"); } if title.len() > 50 { panic!("Title cannot be longer than 50 bytes"); } if description.is_empty() { panic!("Description cannot be empty"); } if description.len() > 500 { panic!("Description cannot be longer than 500 bytes"); } if status != "To-Do" && status != "In Progress" && status != "Done" { panic!("Only `To-Do`, `In Progress`, and `Done` statuses are allowed"); } Ticket { title, description, status, } } pub fn title(&self) -> &String { &self.title } pub fn description(&self) -> &String { &self.description } pub fn status(&self) -> &String { &self.status } } #[cfg(test)] mod tests { use super::*; use common::{valid_description, valid_title}; #[test] fn test_partial_eq() { let title = valid_title(); let description = valid_description(); let ticket1 = Ticket { title: title.clone(), description: description.clone(), status: Status::ToDo, }; let ticket2 = Ticket { title: title.clone(), description: description.clone(), status: Status::ToDo, }; assert_eq!(ticket1, ticket2); } #[test] fn test_description_not_matching() { let title = valid_title(); let status = Status::ToDo; let ticket1 = Ticket { title: title.clone(), description: "description".to_string(), status, }; let ticket2 = Ticket { title: title.clone(), description: "description2".to_string(), status, }; assert_ne!(ticket1, ticket2); } #[test] fn test_title_not_matching() { let description = valid_description(); let status = Status::InProgress; let ticket1 = Ticket { title: "title".to_string(), description: description.clone(), status, }; let ticket2 = Ticket { title: "title2".to_string(), description: description.clone(), status, }; assert_ne!(ticket1, ticket2); } #[test] fn test_status_not_matching() { let title = valid_title(); let description = valid_description(); let ticket1 = Ticket { title: title.clone(), description: description.clone(), status: Status::InProgress, }; let ticket2 = Ticket { title: title.clone(), description: description.clone(), status: Status::Done, }; assert_ne!(ticket1, ticket2); } } ================================================ FILE: exercises/05_ticket_v2/02_match/Cargo.toml ================================================ [package] name = "match_" version = "0.1.0" edition = "2021" ================================================ FILE: exercises/05_ticket_v2/02_match/src/lib.rs ================================================ enum Shape { Circle, Square, Rectangle, Triangle, Pentagon, } impl Shape { // TODO: Implement the `n_sides` method using a `match`. pub fn n_sides(&self) -> u8 { todo!() } } #[cfg(test)] mod tests { use super::*; #[test] fn test_circle() { assert_eq!(Shape::Circle.n_sides(), 0); } #[test] fn test_square() { assert_eq!(Shape::Square.n_sides(), 4); } #[test] fn test_rectangle() { assert_eq!(Shape::Rectangle.n_sides(), 4); } #[test] fn test_triangle() { assert_eq!(Shape::Triangle.n_sides(), 3); } #[test] fn test_pentagon() { assert_eq!(Shape::Pentagon.n_sides(), 5); } } ================================================ FILE: exercises/05_ticket_v2/03_variants_with_data/Cargo.toml ================================================ [package] name = "variants_with_data" version = "0.1.0" edition = "2021" [dev-dependencies] common = { path = "../../../helpers/common" } ================================================ FILE: exercises/05_ticket_v2/03_variants_with_data/src/lib.rs ================================================ // TODO: Implement `Ticket::assigned_to`. // Return the name of the person assigned to the ticket, if the ticket is in progress. // Panic otherwise. #[derive(Debug, PartialEq)] struct Ticket { title: String, description: String, status: Status, } #[derive(Debug, PartialEq)] enum Status { ToDo, InProgress { assigned_to: String }, Done, } impl Ticket { pub fn new(title: String, description: String, status: Status) -> Ticket { if title.is_empty() { panic!("Title cannot be empty"); } if title.len() > 50 { panic!("Title cannot be longer than 50 bytes"); } if description.is_empty() { panic!("Description cannot be empty"); } if description.len() > 500 { panic!("Description cannot be longer than 500 bytes"); } Ticket { title, description, status, } } pub fn assigned_to(&self) -> &str { todo!() } } #[cfg(test)] mod tests { use super::*; use common::{valid_description, valid_title}; #[test] #[should_panic(expected = "Only `In-Progress` tickets can be assigned to someone")] fn test_todo() { let ticket = Ticket::new(valid_title(), valid_description(), Status::ToDo); ticket.assigned_to(); } #[test] #[should_panic(expected = "Only `In-Progress` tickets can be assigned to someone")] fn test_done() { let ticket = Ticket::new(valid_title(), valid_description(), Status::Done); ticket.assigned_to(); } #[test] fn test_in_progress() { let ticket = Ticket::new( valid_title(), valid_description(), Status::InProgress { assigned_to: "Alice".to_string(), }, ); assert_eq!(ticket.assigned_to(), "Alice"); } } ================================================ FILE: exercises/05_ticket_v2/04_if_let/Cargo.toml ================================================ [package] name = "if_let" version = "0.1.0" edition = "2021" ================================================ FILE: exercises/05_ticket_v2/04_if_let/src/lib.rs ================================================ enum Shape { Circle { radius: f64 }, Square { border: f64 }, Rectangle { width: f64, height: f64 }, } impl Shape { // TODO: Implement the `radius` method using // either an `if let` or a `let/else`. pub fn radius(&self) -> f64 { todo!() } } #[cfg(test)] mod tests { use super::*; #[test] fn test_circle() { let _ = Shape::Circle { radius: 1.0 }.radius(); } #[test] #[should_panic] fn test_square() { let _ = Shape::Square { border: 1.0 }.radius(); } #[test] #[should_panic] fn test_rectangle() { let _ = Shape::Rectangle { width: 1.0, height: 2.0, } .radius(); } } ================================================ FILE: exercises/05_ticket_v2/05_nullability/Cargo.toml ================================================ [package] name = "nullability" version = "0.1.0" edition = "2021" [dev-dependencies] common = { path = "../../../helpers/common" } ================================================ FILE: exercises/05_ticket_v2/05_nullability/src/lib.rs ================================================ // TODO: Implement `Ticket::assigned_to` using `Option` as the return type. #[derive(Debug, PartialEq)] struct Ticket { title: String, description: String, status: Status, } #[derive(Debug, PartialEq)] enum Status { ToDo, InProgress { assigned_to: String }, Done, } impl Ticket { pub fn new(title: String, description: String, status: Status) -> Ticket { if title.is_empty() { panic!("Title cannot be empty"); } if title.len() > 50 { panic!("Title cannot be longer than 50 bytes"); } if description.is_empty() { panic!("Description cannot be empty"); } if description.len() > 500 { panic!("Description cannot be longer than 500 bytes"); } Ticket { title, description, status, } } pub fn assigned_to(&self) -> Option<&String> { todo!() } } #[cfg(test)] mod tests { use super::*; use common::{valid_description, valid_title}; #[test] fn test_todo() { let ticket = Ticket::new(valid_title(), valid_description(), Status::ToDo); assert!(ticket.assigned_to().is_none()); } #[test] fn test_done() { let ticket = Ticket::new(valid_title(), valid_description(), Status::Done); assert!(ticket.assigned_to().is_none()); } #[test] fn test_in_progress() { let ticket = Ticket::new( valid_title(), valid_description(), Status::InProgress { assigned_to: "Alice".to_string(), }, ); assert_eq!(ticket.assigned_to(), Some(&"Alice".to_string())); } } ================================================ FILE: exercises/05_ticket_v2/06_fallibility/Cargo.toml ================================================ [package] name = "fallibility" version = "0.1.0" edition = "2021" [dev-dependencies] common = { path = "../../../helpers/common" } ================================================ FILE: exercises/05_ticket_v2/06_fallibility/src/lib.rs ================================================ // TODO: Convert the `Ticket::new` method to return a `Result` instead of panicking. // Use `String` as the error type. #[derive(Debug, PartialEq)] struct Ticket { title: String, description: String, status: Status, } #[derive(Debug, PartialEq)] enum Status { ToDo, InProgress { assigned_to: String }, Done, } impl Ticket { pub fn new(title: String, description: String, status: Status) -> Ticket { if title.is_empty() { panic!("Title cannot be empty"); } if title.len() > 50 { panic!("Title cannot be longer than 50 bytes"); } if description.is_empty() { panic!("Description cannot be empty"); } if description.len() > 500 { panic!("Description cannot be longer than 500 bytes"); } Ticket { title, description, status, } } } #[cfg(test)] mod tests { use super::*; use common::{overly_long_description, overly_long_title, valid_description, valid_title}; #[test] fn title_cannot_be_empty() { let error = Ticket::new("".into(), valid_description(), Status::ToDo).unwrap_err(); assert_eq!(error, "Title cannot be empty"); } #[test] fn description_cannot_be_empty() { let error = Ticket::new(valid_title(), "".into(), Status::ToDo).unwrap_err(); assert_eq!(error, "Description cannot be empty"); } #[test] fn title_cannot_be_longer_than_fifty_chars() { let error = Ticket::new(overly_long_title(), valid_description(), Status::ToDo).unwrap_err(); assert_eq!(error, "Title cannot be longer than 50 bytes"); } #[test] fn description_cannot_be_longer_than_500_chars() { let error = Ticket::new(valid_title(), overly_long_description(), Status::ToDo).unwrap_err(); assert_eq!(error, "Description cannot be longer than 500 bytes"); } } ================================================ FILE: exercises/05_ticket_v2/07_unwrap/Cargo.toml ================================================ [package] name = "unwrap" version = "0.1.0" edition = "2021" [dev-dependencies] common = { path = "../../../helpers/common" } ================================================ FILE: exercises/05_ticket_v2/07_unwrap/src/lib.rs ================================================ // TODO: `easy_ticket` should panic when the title is invalid. // When the description is invalid, instead, it should use a default description: // "Description not provided". fn easy_ticket(title: String, description: String, status: Status) -> Ticket { todo!() } #[derive(Debug, PartialEq, Clone)] struct Ticket { title: String, description: String, status: Status, } #[derive(Debug, PartialEq, Clone)] enum Status { ToDo, InProgress { assigned_to: String }, Done, } impl Ticket { pub fn new(title: String, description: String, status: Status) -> Result { if title.is_empty() { return Err("Title cannot be empty".to_string()); } if title.len() > 50 { return Err("Title cannot be longer than 50 bytes".to_string()); } if description.is_empty() { return Err("Description cannot be empty".to_string()); } if description.len() > 500 { return Err("Description cannot be longer than 500 bytes".to_string()); } Ok(Ticket { title, description, status, }) } } #[cfg(test)] mod tests { use super::*; use common::{overly_long_description, overly_long_title, valid_description, valid_title}; #[test] #[should_panic(expected = "Title cannot be empty")] fn title_cannot_be_empty() { easy_ticket("".into(), valid_description(), Status::ToDo); } #[test] fn template_description_is_used_if_empty() { let ticket = easy_ticket(valid_title(), "".into(), Status::ToDo); assert_eq!(ticket.description, "Description not provided"); } #[test] #[should_panic(expected = "Title cannot be longer than 50 bytes")] fn title_cannot_be_longer_than_fifty_chars() { easy_ticket(overly_long_title(), valid_description(), Status::ToDo); } #[test] fn template_description_is_used_if_too_long() { let ticket = easy_ticket(valid_title(), overly_long_description(), Status::ToDo); assert_eq!(ticket.description, "Description not provided"); } } ================================================ FILE: exercises/05_ticket_v2/08_error_enums/Cargo.toml ================================================ [package] name = "error_enums" version = "0.1.0" edition = "2021" [dev-dependencies] common = { path = "../../../helpers/common" } ================================================ FILE: exercises/05_ticket_v2/08_error_enums/src/lib.rs ================================================ // TODO: Use two variants, one for a title error and one for a description error. // Each variant should contain a string with the explanation of what went wrong exactly. // You'll have to update the implementation of `Ticket::new` as well. enum TicketNewError {} // TODO: `easy_ticket` should panic when the title is invalid, using the error message // stored inside the relevant variant of the `TicketNewError` enum. // When the description is invalid, instead, it should use a default description: // "Description not provided". fn easy_ticket(title: String, description: String, status: Status) -> Ticket { todo!() } #[derive(Debug, PartialEq)] struct Ticket { title: String, description: String, status: Status, } #[derive(Debug, PartialEq, Clone)] enum Status { ToDo, InProgress { assigned_to: String }, Done, } impl Ticket { pub fn new( title: String, description: String, status: Status, ) -> Result { if title.is_empty() { return Err("Title cannot be empty".to_string()); } if title.len() > 50 { return Err("Title cannot be longer than 50 bytes".to_string()); } if description.is_empty() { return Err("Description cannot be empty".to_string()); } if description.len() > 500 { return Err("Description cannot be longer than 500 bytes".to_string()); } Ok(Ticket { title, description, status, }) } } #[cfg(test)] mod tests { use super::*; use common::{overly_long_description, overly_long_title, valid_description, valid_title}; #[test] #[should_panic(expected = "Title cannot be empty")] fn title_cannot_be_empty() { easy_ticket("".into(), valid_description(), Status::ToDo); } #[test] fn template_description_is_used_if_empty() { let ticket = easy_ticket(valid_title(), "".into(), Status::ToDo); assert_eq!(ticket.description, "Description not provided"); } #[test] #[should_panic(expected = "Title cannot be longer than 50 bytes")] fn title_cannot_be_longer_than_fifty_chars() { easy_ticket(overly_long_title(), valid_description(), Status::ToDo); } #[test] fn template_description_is_used_if_too_long() { let ticket = easy_ticket(valid_title(), overly_long_description(), Status::ToDo); assert_eq!(ticket.description, "Description not provided"); } } ================================================ FILE: exercises/05_ticket_v2/09_error_trait/Cargo.toml ================================================ [package] name = "error_trait" version = "0.1.0" edition = "2021" [dev-dependencies] common = { path = "../../../helpers/common" } static_assertions = "1.1.0" ================================================ FILE: exercises/05_ticket_v2/09_error_trait/src/lib.rs ================================================ // TODO: Implement `Debug`, `Display` and `Error` for the `TicketNewError` enum. // When implementing `Display`, you may want to use the `write!` macro from Rust's standard library. // The docs for the `std::fmt` module are a good place to start and look for examples: // https://doc.rust-lang.org/std/fmt/index.html#write enum TicketNewError { TitleError(String), DescriptionError(String), } // TODO: `easy_ticket` should panic when the title is invalid, using the error message // stored inside the relevant variant of the `TicketNewError` enum. // When the description is invalid, instead, it should use a default description: // "Description not provided". fn easy_ticket(title: String, description: String, status: Status) -> Ticket { todo!() } #[derive(Debug, PartialEq, Clone)] struct Ticket { title: String, description: String, status: Status, } #[derive(Debug, PartialEq, Clone)] enum Status { ToDo, InProgress { assigned_to: String }, Done, } impl Ticket { pub fn new( title: String, description: String, status: Status, ) -> Result { if title.is_empty() { return Err(TicketNewError::TitleError( "Title cannot be empty".to_string(), )); } if title.len() > 50 { return Err(TicketNewError::TitleError( "Title cannot be longer than 50 bytes".to_string(), )); } if description.is_empty() { return Err(TicketNewError::DescriptionError( "Description cannot be empty".to_string(), )); } if description.len() > 500 { return Err(TicketNewError::DescriptionError( "Description cannot be longer than 500 bytes".to_string(), )); } Ok(Ticket { title, description, status, }) } } #[cfg(test)] mod tests { use super::*; use common::{overly_long_description, overly_long_title, valid_description, valid_title}; use static_assertions::assert_impl_one; #[test] #[should_panic(expected = "Title cannot be empty")] fn title_cannot_be_empty() { easy_ticket("".into(), valid_description(), Status::ToDo); } #[test] fn template_description_is_used_if_empty() { let ticket = easy_ticket(valid_title(), "".into(), Status::ToDo); assert_eq!(ticket.description, "Description not provided"); } #[test] #[should_panic(expected = "Title cannot be longer than 50 bytes")] fn title_cannot_be_longer_than_fifty_chars() { easy_ticket(overly_long_title(), valid_description(), Status::ToDo); } #[test] fn template_description_is_used_if_too_long() { let ticket = easy_ticket(valid_title(), overly_long_description(), Status::ToDo); assert_eq!(ticket.description, "Description not provided"); } #[test] fn display_is_correctly_implemented() { let ticket = Ticket::new("".into(), valid_description(), Status::ToDo); assert_eq!(format!("{}", ticket.unwrap_err()), "Title cannot be empty"); } assert_impl_one!(TicketNewError: std::error::Error); } ================================================ FILE: exercises/05_ticket_v2/10_packages/Cargo.toml ================================================ [package] name = "packages" version = "0.1.0" edition = "2021" ================================================ FILE: exercises/05_ticket_v2/10_packages/src/main.rs ================================================ // This is a `main.rs` file, therefore `cargo` interprets this as the root of a binary target. // TODO: fix this broken import. Create a new library target in the `src` directory. // The library target should expose a public function named `hello_world` that takes no arguments // and returns nothing. use packages::hello_world; // This is the entrypoint of the binary. fn main() { hello_world(); } ================================================ FILE: exercises/05_ticket_v2/11_dependencies/Cargo.toml ================================================ [package] name = "deps" version = "0.1.0" edition = "2021" ================================================ FILE: exercises/05_ticket_v2/11_dependencies/src/lib.rs ================================================ // TODO: Add `anyhow` as a dependency of this project. // Don't touch this import! // When you import a type (`Error`) from a dependency, the import path must start // with the crate name (`anyhow`, in this case). use anyhow::Error; ================================================ FILE: exercises/05_ticket_v2/12_thiserror/Cargo.toml ================================================ [package] name = "thiserror_" version = "0.1.0" edition = "2021" [dependencies] [dev-dependencies] common = { path = "../../../helpers/common" } ================================================ FILE: exercises/05_ticket_v2/12_thiserror/src/lib.rs ================================================ // TODO: Implement the `Error` trait for `TicketNewError` using `thiserror`. // We've changed the enum variants to be more specific, thus removing the need for storing // a `String` field into each variant. // You'll also have to add `thiserror` as a dependency in the `Cargo.toml` file. enum TicketNewError { TitleCannotBeEmpty, TitleTooLong, DescriptionCannotBeEmpty, DescriptionTooLong, } #[derive(Debug, PartialEq, Clone)] struct Ticket { title: String, description: String, status: Status, } #[derive(Debug, PartialEq, Clone)] enum Status { ToDo, InProgress { assigned_to: String }, Done, } impl Ticket { pub fn new( title: String, description: String, status: Status, ) -> Result { if title.is_empty() { return Err(TicketNewError::TitleCannotBeEmpty); } if title.len() > 50 { return Err(TicketNewError::TitleTooLong); } if description.is_empty() { return Err(TicketNewError::DescriptionCannotBeEmpty); } if description.len() > 500 { return Err(TicketNewError::DescriptionTooLong); } Ok(Ticket { title, description, status, }) } } #[cfg(test)] mod tests { use super::*; use common::{overly_long_description, overly_long_title, valid_description, valid_title}; #[test] fn title_cannot_be_empty() { let err = Ticket::new("".into(), valid_description(), Status::ToDo).unwrap_err(); assert_eq!(err.to_string(), "Title cannot be empty"); } #[test] fn description_cannot_be_empty() { let err = Ticket::new(valid_title(), "".into(), Status::ToDo).unwrap_err(); assert_eq!(err.to_string(), "Description cannot be empty"); } #[test] fn title_cannot_be_longer_than_fifty_chars() { let err = Ticket::new(overly_long_title(), valid_description(), Status::ToDo).unwrap_err(); assert_eq!(err.to_string(), "Title cannot be longer than 50 bytes"); } #[test] fn description_cannot_be_too_long() { let err = Ticket::new(valid_title(), overly_long_description(), Status::ToDo).unwrap_err(); assert_eq!( err.to_string(), "Description cannot be longer than 500 bytes" ); } } ================================================ FILE: exercises/05_ticket_v2/13_try_from/Cargo.toml ================================================ [package] name = "tryfrom" version = "0.1.0" edition = "2021" [dependencies] ================================================ FILE: exercises/05_ticket_v2/13_try_from/src/lib.rs ================================================ // TODO: Implement `TryFrom` and `TryFrom<&str>` for `Status`. // The parsing should be case-insensitive. #[derive(Debug, PartialEq, Clone)] enum Status { ToDo, InProgress, Done, } #[cfg(test)] mod tests { use super::*; use std::convert::TryFrom; #[test] fn test_try_from_string() { let status = Status::try_from("ToDO".to_string()).unwrap(); assert_eq!(status, Status::ToDo); let status = Status::try_from("inproGress".to_string()).unwrap(); assert_eq!(status, Status::InProgress); let status = Status::try_from("Done".to_string()).unwrap(); assert_eq!(status, Status::Done); } #[test] fn test_try_from_str() { let status = Status::try_from("todo").unwrap(); assert_eq!(status, Status::ToDo); let status = Status::try_from("inprogress").unwrap(); assert_eq!(status, Status::InProgress); let status = Status::try_from("done").unwrap(); assert_eq!(status, Status::Done); } } ================================================ FILE: exercises/05_ticket_v2/14_source/Cargo.toml ================================================ [package] name = "source" version = "0.1.0" edition = "2021" [dependencies] thiserror = "1.0.69" [dev-dependencies] common = { path = "../../../helpers/common" } ================================================ FILE: exercises/05_ticket_v2/14_source/src/lib.rs ================================================ use crate::status::Status; // We've seen how to declare modules in one of the earliest exercises, but // we haven't seen how to extract them into separate files. // Let's fix that now! // // In the simplest case, when the extracted module is a single file, it is enough to // create a new file with the same name as the module and move the module content there. // The module file should be placed in the same directory as the file that declares the module. // In this case, `src/lib.rs`, thus `status.rs` should be placed in the `src` directory. mod status; // TODO: Add a new error variant to `TicketNewError` for when the status string is invalid. // When calling `source` on an error of that variant, it should return a `ParseStatusError` rather than `None`. #[derive(Debug, thiserror::Error)] pub enum TicketNewError { #[error("Title cannot be empty")] TitleCannotBeEmpty, #[error("Title cannot be longer than 50 bytes")] TitleTooLong, #[error("Description cannot be empty")] DescriptionCannotBeEmpty, #[error("Description cannot be longer than 500 bytes")] DescriptionTooLong, } #[derive(Debug, PartialEq, Clone)] pub struct Ticket { title: String, description: String, status: Status, } impl Ticket { pub fn new(title: String, description: String, status: String) -> Result { if title.is_empty() { return Err(TicketNewError::TitleCannotBeEmpty); } if title.len() > 50 { return Err(TicketNewError::TitleTooLong); } if description.is_empty() { return Err(TicketNewError::DescriptionCannotBeEmpty); } if description.len() > 500 { return Err(TicketNewError::DescriptionTooLong); } // TODO: Parse the status string into a `Status` enum. Ok(Ticket { title, description, status, }) } } #[cfg(test)] mod tests { use common::{valid_description, valid_title}; use std::error::Error; use super::*; #[test] fn invalid_status() { let err = Ticket::new(valid_title(), valid_description(), "invalid".into()).unwrap_err(); assert_eq!( err.to_string(), "`invalid` is not a valid status. Use one of: ToDo, InProgress, Done" ); assert!(err.source().is_some()); } } ================================================ FILE: exercises/05_ticket_v2/14_source/src/status.rs ================================================ #[derive(Debug, PartialEq, Clone)] pub enum Status { ToDo, InProgress, Done, } impl TryFrom for Status { type Error = ParseStatusError; fn try_from(value: String) -> Result { let value = value.to_lowercase(); match value.as_str() { "todo" => Ok(Status::ToDo), "inprogress" => Ok(Status::InProgress), "done" => Ok(Status::Done), _ => Err(ParseStatusError { invalid_status: value, }), } } } #[derive(Debug, thiserror::Error)] #[error("`{invalid_status}` is not a valid status. Use one of: ToDo, InProgress, Done")] pub struct ParseStatusError { invalid_status: String, } #[cfg(test)] mod tests { use super::*; use std::convert::TryFrom; #[test] fn test_try_from_string() { let status = Status::try_from("ToDO".to_string()).unwrap(); assert_eq!(status, Status::ToDo); let status = Status::try_from("inproGress".to_string()).unwrap(); assert_eq!(status, Status::InProgress); let status = Status::try_from("Done".to_string()).unwrap(); assert_eq!(status, Status::Done); } } ================================================ FILE: exercises/05_ticket_v2/15_outro/Cargo.toml ================================================ [package] name = "outro_04" version = "0.1.0" edition = "2021" ================================================ FILE: exercises/05_ticket_v2/15_outro/src/description.rs ================================================ // TODO: Implement `TryFrom` and `TryFrom<&str>` for the `TicketDescription` type, // enforcing that the description is not empty and is not longer than 500 bytes. // Implement the traits required to make the tests pass too. pub struct TicketDescription(String); #[cfg(test)] mod tests { use super::*; use std::convert::TryFrom; #[test] fn test_try_from_string() { let description = TicketDescription::try_from("A description".to_string()).unwrap(); assert_eq!(description.0, "A description"); } #[test] fn test_try_from_empty_string() { let err = TicketDescription::try_from("".to_string()).unwrap_err(); assert_eq!(err.to_string(), "The description cannot be empty"); } #[test] fn test_try_from_long_string() { let description = "At vero eos et accusamus et iusto odio dignissimos ducimus qui blanditiis praesentium voluptatum deleniti atque corrupti quos dolores et quas molestias excepturi sint occaecati cupiditate non provident, similique sunt in culpa qui officia deserunt mollitia animi, id est laborum et dolorum fuga. Et harum quidem rerum facilis est et expedita distinctio. Nam libero tempore, cum soluta nobis est eligendi optio cumque nihil impedit quo minus id quod maxime placeat facere possimus, omnis voluptas assumenda est, omnis dolor repellendus. Temporibus autem quibusdam et aut officiis debitis aut rerum necessitatibus saepe eveniet ut et voluptates repudiandae sint et molestiae non recusandae. Itaque earum rerum hic tenetur a sapiente delectus, ut aut reiciendis voluptatibus maiores alias consequatur aut perferendis doloribus asperiores repellat.".to_string(); let err = TicketDescription::try_from(description).unwrap_err(); assert_eq!( err.to_string(), "The description cannot be longer than 500 bytes" ); } #[test] fn test_try_from_str() { let description = TicketDescription::try_from("A description").unwrap(); assert_eq!(description.0, "A description"); } } ================================================ FILE: exercises/05_ticket_v2/15_outro/src/lib.rs ================================================ // TODO: you have something to do in each of the modules in this crate! mod description; mod status; mod title; // A common pattern in Rust is to split code into multiple (private) modules // and then re-export the public parts of those modules at the root of the crate. // // This hides the internal structure of the crate from your users, while still // allowing you to organize your code however you like. pub use description::TicketDescription; pub use status::Status; pub use title::TicketTitle; #[derive(Debug, PartialEq, Clone)] // We no longer need to make the fields private! // Since each field encapsulates its own validation logic, there is no risk of // a user of `Ticket` modifying the fields in a way that would break the // invariants of the struct. // // Careful though: if you had any invariants that spanned multiple fields, you // would need to ensure that those invariants are still maintained and go back // to making the fields private. pub struct Ticket { pub title: TicketTitle, pub description: TicketDescription, pub status: Status, } ================================================ FILE: exercises/05_ticket_v2/15_outro/src/status.rs ================================================ // TODO: Implement `TryFrom` and `TryFrom<&str>` for the `Status` enum. // The parsing should be case-insensitive. pub enum Status { ToDo, InProgress, Done, } #[cfg(test)] mod tests { use super::*; use std::convert::TryFrom; #[test] fn test_try_from_string() { let status = Status::try_from("ToDO".to_string()).unwrap(); assert_eq!(status, Status::ToDo); let status = Status::try_from("inproGress".to_string()).unwrap(); assert_eq!(status, Status::InProgress); let status = Status::try_from("Done".to_string()).unwrap(); assert_eq!(status, Status::Done); } #[test] fn test_try_from_str() { let status = Status::try_from("ToDO").unwrap(); assert_eq!(status, Status::ToDo); let status = Status::try_from("inproGress").unwrap(); assert_eq!(status, Status::InProgress); let status = Status::try_from("Done").unwrap(); assert_eq!(status, Status::Done); } #[test] fn test_try_from_invalid() { let status = Status::try_from("Invalid"); assert!(status.is_err()); } } ================================================ FILE: exercises/05_ticket_v2/15_outro/src/title.rs ================================================ // TODO: Implement `TryFrom` and `TryFrom<&str>` for the `TicketTitle` type, // enforcing that the title is not empty and is not longer than 50 bytes. // Implement the traits required to make the tests pass too. pub struct TicketTitle(String); #[cfg(test)] mod tests { use super::*; use std::convert::TryFrom; #[test] fn test_try_from_string() { let title = TicketTitle::try_from("A title".to_string()).unwrap(); assert_eq!(title.0, "A title"); } #[test] fn test_try_from_empty_string() { let err = TicketTitle::try_from("".to_string()).unwrap_err(); assert_eq!(err.to_string(), "The title cannot be empty"); } #[test] fn test_try_from_long_string() { let title = "A title that's definitely longer than what should be allowed in a development ticket" .to_string(); let err = TicketTitle::try_from(title).unwrap_err(); assert_eq!(err.to_string(), "The title cannot be longer than 50 bytes"); } #[test] fn test_try_from_str() { let title = TicketTitle::try_from("A title").unwrap(); assert_eq!(title.0, "A title"); } } ================================================ FILE: exercises/06_ticket_management/00_intro/Cargo.toml ================================================ [package] name = "intro_05" version = "0.1.0" edition = "2021" ================================================ FILE: exercises/06_ticket_management/00_intro/src/lib.rs ================================================ fn intro() -> &'static str { // TODO: fix me 👇 "I'm ready to __!" } #[cfg(test)] mod tests { use crate::intro; #[test] fn test_intro() { assert_eq!(intro(), "I'm ready to build a ticket management system!"); } } ================================================ FILE: exercises/06_ticket_management/01_arrays/Cargo.toml ================================================ [package] name = "arrays" version = "0.1.0" edition = "2021" ================================================ FILE: exercises/06_ticket_management/01_arrays/src/lib.rs ================================================ // TODO: Flesh out the `WeekTemperatures` struct and its method implementations to pass the tests. pub struct WeekTemperatures { // TODO } pub enum Weekday { Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday, } impl WeekTemperatures { pub fn new() -> Self { todo!() } pub fn get_temperature(&self, day: Weekday) -> Option { todo!() } pub fn set_temperature(&mut self, day: Weekday, temperature: i32) { todo!() } } #[cfg(test)] mod tests { use super::*; #[test] fn test_get_temperature() { let mut week_temperatures = WeekTemperatures::new(); assert_eq!(week_temperatures.get_temperature(Weekday::Monday), None); assert_eq!(week_temperatures.get_temperature(Weekday::Tuesday), None); assert_eq!(week_temperatures.get_temperature(Weekday::Wednesday), None); assert_eq!(week_temperatures.get_temperature(Weekday::Thursday), None); assert_eq!(week_temperatures.get_temperature(Weekday::Friday), None); assert_eq!(week_temperatures.get_temperature(Weekday::Saturday), None); assert_eq!(week_temperatures.get_temperature(Weekday::Sunday), None); week_temperatures.set_temperature(Weekday::Monday, 20); assert_eq!(week_temperatures.get_temperature(Weekday::Monday), Some(20)); week_temperatures.set_temperature(Weekday::Monday, 25); assert_eq!(week_temperatures.get_temperature(Weekday::Monday), Some(25)); week_temperatures.set_temperature(Weekday::Tuesday, 30); week_temperatures.set_temperature(Weekday::Wednesday, 35); week_temperatures.set_temperature(Weekday::Thursday, 40); week_temperatures.set_temperature(Weekday::Friday, 45); week_temperatures.set_temperature(Weekday::Saturday, 50); week_temperatures.set_temperature(Weekday::Sunday, 55); assert_eq!(week_temperatures.get_temperature(Weekday::Monday), Some(25)); assert_eq!( week_temperatures.get_temperature(Weekday::Tuesday), Some(30) ); assert_eq!( week_temperatures.get_temperature(Weekday::Wednesday), Some(35) ); assert_eq!( week_temperatures.get_temperature(Weekday::Thursday), Some(40) ); assert_eq!(week_temperatures.get_temperature(Weekday::Friday), Some(45)); assert_eq!( week_temperatures.get_temperature(Weekday::Saturday), Some(50) ); assert_eq!(week_temperatures.get_temperature(Weekday::Sunday), Some(55)); } } ================================================ FILE: exercises/06_ticket_management/02_vec/Cargo.toml ================================================ [package] name = "vec" version = "0.1.0" edition = "2021" ================================================ FILE: exercises/06_ticket_management/02_vec/src/lib.rs ================================================ // Given a number `n`, return the `n+1`th number in the Fibonacci sequence. // // The Fibonacci sequence is defined as follows: // // - The first number of the sequence is 0. // - The second number of the sequence is 1. // - Every subsequent number is the sum of the two preceding numbers. // // So the sequence goes: 0, 1, 1, 2, 3, 5, 8, 13, 21, and so on. // // We expect `fibonacci(0)` to return `0`, `fibonacci(1)` to return `1`, // `fibonacci(2)` to return `1`, and so on. pub fn fibonacci(n: u32) -> u32 { // TODO: implement the `fibonacci` function // // Hint: use a `Vec` to memoize the results you have already calculated // so that you don't have to recalculate them several times. todo!() } #[cfg(test)] mod tests { use crate::fibonacci; #[test] fn first() { assert_eq!(fibonacci(0), 0); } #[test] fn second() { assert_eq!(fibonacci(1), 1); } #[test] fn third() { assert_eq!(fibonacci(2), 1); } #[test] fn tenth() { assert_eq!(fibonacci(10), 55); } #[test] fn thirtieth() { assert_eq!(fibonacci(30), 832040); } } ================================================ FILE: exercises/06_ticket_management/03_resizing/Cargo.toml ================================================ [package] name = "resizing" version = "0.1.0" edition = "2021" ================================================ FILE: exercises/06_ticket_management/03_resizing/src/lib.rs ================================================ #[cfg(test)] mod tests { #[test] fn resizing() { let mut v = Vec::with_capacity(2); v.push(1); v.push(2); // max capacity reached assert_eq!(v.capacity(), 2); v.push(3); // beyond capacity, needs to resize // Can you guess what the new capacity will be? // Beware that the standard library makes no guarantees about the // algorithm used to resize the vector, so this may change in the future. assert_eq!(v.capacity(), todo!()); } } ================================================ FILE: exercises/06_ticket_management/04_iterators/Cargo.toml ================================================ [package] name = "iterators" version = "0.1.0" edition = "2021" [dependencies] ticket_fields = { path = "../../../helpers/ticket_fields" } ================================================ FILE: exercises/06_ticket_management/04_iterators/src/lib.rs ================================================ use ticket_fields::{TicketDescription, TicketTitle}; // TODO: Let's start sketching our ticket store! // First task: implement `IntoIterator` on `TicketStore` to allow iterating over all the tickets // it contains using a `for` loop. // // Hint: you shouldn't have to implement the `Iterator` trait in this case. // You want to *delegate* the iteration to the `Vec` field in `TicketStore`. // Look at the standard library documentation for `Vec` to find the right type // to return from `into_iter`. #[derive(Clone)] pub struct TicketStore { tickets: Vec, } #[derive(Clone, Debug, PartialEq)] pub struct Ticket { pub title: TicketTitle, pub description: TicketDescription, pub status: Status, } #[derive(Clone, Debug, Copy, PartialEq)] pub enum Status { ToDo, InProgress, Done, } impl TicketStore { pub fn new() -> Self { Self { tickets: Vec::new(), } } pub fn add_ticket(&mut self, ticket: Ticket) { self.tickets.push(ticket); } } #[cfg(test)] mod tests { use super::*; use ticket_fields::test_helpers::{ticket_description, ticket_title}; #[test] fn add_ticket() { let mut store = TicketStore::new(); let ticket = Ticket { title: ticket_title(), description: ticket_description(), status: Status::ToDo, }; store.add_ticket(ticket); let ticket = Ticket { title: ticket_title(), description: ticket_description(), status: Status::InProgress, }; store.add_ticket(ticket); let tickets: Vec<_> = store.clone().into_iter().collect(); assert_eq!(tickets, store.tickets); } } ================================================ FILE: exercises/06_ticket_management/05_iter/Cargo.toml ================================================ [package] name = "iter" version = "0.1.0" edition = "2021" [dependencies] ticket_fields = { path = "../../../helpers/ticket_fields" } ================================================ FILE: exercises/06_ticket_management/05_iter/src/lib.rs ================================================ use ticket_fields::{TicketDescription, TicketTitle}; // TODO: Provide an `iter` method that returns an iterator over `&Ticket` items. // // Hint: just like in the previous exercise, you want to delegate the iteration to // the `Vec` field in `TicketStore`. Look at the standard library documentation // for `Vec` to find the right type to return from `iter`. #[derive(Clone)] pub struct TicketStore { tickets: Vec, } #[derive(Debug, Clone, PartialEq)] pub struct Ticket { title: TicketTitle, description: TicketDescription, status: Status, } #[derive(Clone, Debug, Copy, PartialEq)] pub enum Status { ToDo, InProgress, Done, } impl TicketStore { pub fn new() -> Self { Self { tickets: Vec::new(), } } pub fn add_ticket(&mut self, ticket: Ticket) { self.tickets.push(ticket); } } #[cfg(test)] mod tests { use super::*; use ticket_fields::test_helpers::{ticket_description, ticket_title}; #[test] fn add_ticket() { let mut store = TicketStore::new(); let ticket = Ticket { title: ticket_title(), description: ticket_description(), status: Status::ToDo, }; store.add_ticket(ticket); let ticket = Ticket { title: ticket_title(), description: ticket_description(), status: Status::InProgress, }; store.add_ticket(ticket); let tickets: Vec<&Ticket> = store.iter().collect(); let tickets2: Vec<&Ticket> = store.iter().collect(); assert_eq!(tickets, tickets2); } } ================================================ FILE: exercises/06_ticket_management/06_lifetimes/Cargo.toml ================================================ [package] name = "lifetime" version = "0.1.0" edition = "2021" [dependencies] ticket_fields = { path = "../../../helpers/ticket_fields" } ================================================ FILE: exercises/06_ticket_management/06_lifetimes/src/lib.rs ================================================ use ticket_fields::{TicketDescription, TicketTitle}; // TODO: Implement the `IntoIterator` trait for `&TicketStore` so that the test compiles and passes. #[derive(Clone)] pub struct TicketStore { tickets: Vec, } #[derive(Clone, Debug, PartialEq)] pub struct Ticket { pub title: TicketTitle, pub description: TicketDescription, pub status: Status, } #[derive(Clone, Debug, Copy, PartialEq)] pub enum Status { ToDo, InProgress, Done, } impl TicketStore { pub fn new() -> Self { Self { tickets: Vec::new(), } } pub fn add_ticket(&mut self, ticket: Ticket) { self.tickets.push(ticket); } pub fn iter(&self) -> std::slice::Iter { self.tickets.iter() } } #[cfg(test)] mod tests { use super::*; use ticket_fields::test_helpers::{ticket_description, ticket_title}; #[test] fn add_ticket() { let mut store = TicketStore::new(); let ticket = Ticket { title: ticket_title(), description: ticket_description(), status: Status::ToDo, }; store.add_ticket(ticket); let ticket = Ticket { title: ticket_title(), description: ticket_description(), status: Status::InProgress, }; store.add_ticket(ticket); let tickets: Vec<&Ticket> = store.iter().collect(); let tickets2: Vec<&Ticket> = (&store).into_iter().collect(); assert_eq!(tickets, tickets2); } } ================================================ FILE: exercises/06_ticket_management/07_combinators/Cargo.toml ================================================ [package] name = "combinators" version = "0.1.0" edition = "2021" [dependencies] ticket_fields = { path = "../../../helpers/ticket_fields" } ================================================ FILE: exercises/06_ticket_management/07_combinators/src/lib.rs ================================================ // TODO: Implement the `to_dos` method. It must return a `Vec` of references to the tickets // in `TicketStore` with status set to `Status::ToDo`. use ticket_fields::{TicketDescription, TicketTitle}; #[derive(Clone)] pub struct TicketStore { tickets: Vec, } #[derive(Clone, Debug, PartialEq)] pub struct Ticket { pub title: TicketTitle, pub description: TicketDescription, pub status: Status, } #[derive(Clone, Debug, Copy, PartialEq)] pub enum Status { ToDo, InProgress, Done, } impl TicketStore { pub fn new() -> Self { Self { tickets: Vec::new(), } } pub fn add_ticket(&mut self, ticket: Ticket) { self.tickets.push(ticket); } } #[cfg(test)] mod tests { use super::*; use ticket_fields::test_helpers::{ticket_description, ticket_title}; #[test] fn todos() { let mut store = TicketStore::new(); let todo = Ticket { title: ticket_title(), description: ticket_description(), status: Status::ToDo, }; store.add_ticket(todo.clone()); let ticket = Ticket { title: ticket_title(), description: ticket_description(), status: Status::InProgress, }; store.add_ticket(ticket); let todos: Vec<&Ticket> = store.to_dos(); assert_eq!(todos.len(), 1); assert_eq!(todos[0], &todo); } } ================================================ FILE: exercises/06_ticket_management/08_impl_trait/Cargo.toml ================================================ [package] name = "impl_trait" version = "0.1.0" edition = "2021" [dependencies] ticket_fields = { path = "../../../helpers/ticket_fields" } ================================================ FILE: exercises/06_ticket_management/08_impl_trait/src/lib.rs ================================================ // TODO: Implement the `in_progress` method. It must return an iterator over the tickets in // `TicketStore` with status set to `Status::InProgress`. use ticket_fields::{TicketDescription, TicketTitle}; #[derive(Clone)] pub struct TicketStore { tickets: Vec, } #[derive(Clone, Debug, PartialEq)] pub struct Ticket { pub title: TicketTitle, pub description: TicketDescription, pub status: Status, } #[derive(Clone, Debug, Copy, PartialEq)] pub enum Status { ToDo, InProgress, Done, } impl TicketStore { pub fn new() -> Self { Self { tickets: Vec::new(), } } pub fn add_ticket(&mut self, ticket: Ticket) { self.tickets.push(ticket); } } #[cfg(test)] mod tests { use super::*; use ticket_fields::test_helpers::{ticket_description, ticket_title}; #[test] fn in_progress() { let mut store = TicketStore::new(); let todo = Ticket { title: ticket_title(), description: ticket_description(), status: Status::ToDo, }; store.add_ticket(todo); let in_progress = Ticket { title: ticket_title(), description: ticket_description(), status: Status::InProgress, }; store.add_ticket(in_progress.clone()); let in_progress_tickets: Vec<&Ticket> = store.in_progress().collect(); assert_eq!(in_progress_tickets.len(), 1); assert_eq!(in_progress_tickets[0], &in_progress); } } ================================================ FILE: exercises/06_ticket_management/09_impl_trait_2/Cargo.toml ================================================ [package] name = "impl_trait_2" version = "0.1.0" edition = "2021" [dependencies] ticket_fields = { path = "../../../helpers/ticket_fields" } ================================================ FILE: exercises/06_ticket_management/09_impl_trait_2/src/lib.rs ================================================ // TODO: Rework the signature of `TicketStore::add_ticket` to use a generic type parameter rather // than `impl Trait` syntax. use ticket_fields::{TicketDescription, TicketTitle}; #[derive(Clone)] pub struct TicketStore { tickets: Vec, } #[derive(Clone, Debug, PartialEq)] pub struct Ticket { pub title: TicketTitle, pub description: TicketDescription, pub status: Status, } #[derive(Clone, Debug, Copy, PartialEq)] pub enum Status { ToDo, InProgress, Done, } impl TicketStore { pub fn new() -> Self { Self { tickets: Vec::new(), } } // Using `Into` as the type parameter for `ticket` allows the method to accept any type // that can be infallibly converted into a `Ticket`. // This can make it nicer to use the method, as it removes the syntax noise of `.into()` // from the calling site. It can worsen the quality of the compiler error messages, though. pub fn add_ticket(&mut self, ticket: impl Into) { self.tickets.push(ticket.into()); } } #[cfg(test)] mod tests { use super::*; use ticket_fields::test_helpers::{ticket_description, ticket_title}; struct TicketDraft { pub title: TicketTitle, pub description: TicketDescription, } impl From for Ticket { fn from(draft: TicketDraft) -> Self { Self { title: draft.title, description: draft.description, status: Status::ToDo, } } } #[test] fn generic_add() { let mut store = TicketStore::new(); // This won't compile if `add_ticket` uses `impl Trait` syntax in argument position. store.add_ticket::(TicketDraft { title: ticket_title(), description: ticket_description(), }); } } ================================================ FILE: exercises/06_ticket_management/10_slices/Cargo.toml ================================================ [package] name = "slice" version = "0.1.0" edition = "2021" ================================================ FILE: exercises/06_ticket_management/10_slices/src/lib.rs ================================================ // TODO: Define a function named `sum` that takes a reference to a slice of `u32` and returns the sum of all // elements in the slice. #[cfg(test)] mod tests { use super::*; #[test] fn empty() { let v = vec![]; assert_eq!(sum(&v), 0); } #[test] fn one_element() { let v = vec![1]; assert_eq!(sum(&v), 1); } #[test] fn multiple_elements() { let v = vec![1, 2, 3, 4, 5]; assert_eq!(sum(&v), 15); } #[test] fn array_slice() { let v = [1, 2, 3, 4, 5]; assert_eq!(sum(&v), 15); } } ================================================ FILE: exercises/06_ticket_management/11_mutable_slices/Cargo.toml ================================================ [package] name = "mut_slice" version = "0.1.0" edition = "2021" ================================================ FILE: exercises/06_ticket_management/11_mutable_slices/src/lib.rs ================================================ // TODO: Define a function named `squared` that raises all `i32`s within a slice to the power of 2. // The slice should be modified in place. #[cfg(test)] mod tests { use super::*; #[test] fn empty() { let mut s = vec![]; squared(&mut s); assert_eq!(s, vec![]); } #[test] fn one() { let mut s = [2]; squared(&mut s); assert_eq!(s, [4]); } #[test] fn multiple() { let mut s = vec![2, 4]; squared(&mut s); assert_eq!(s, vec![4, 16]); } } ================================================ FILE: exercises/06_ticket_management/12_two_states/Cargo.toml ================================================ [package] name = "two_states" version = "0.1.0" edition = "2021" [dependencies] ticket_fields = { path = "../../../helpers/ticket_fields" } ================================================ FILE: exercises/06_ticket_management/12_two_states/src/lib.rs ================================================ // TODO: Update `add_ticket`'s signature: it should take a `TicketDraft` as input // and return a `TicketId` as output. // Each ticket should have a unique id, generated by `TicketStore`. // Feel free to modify `TicketStore` fields, if needed. // // You also need to add a `get` method that takes as input a `TicketId` // and returns an `Option<&Ticket>`. use ticket_fields::{TicketDescription, TicketTitle}; #[derive(Clone)] pub struct TicketStore { tickets: Vec, } #[derive(Clone, Copy, Debug, PartialEq)] pub struct TicketId(u64); #[derive(Clone, Debug, PartialEq)] pub struct Ticket { pub id: TicketId, pub title: TicketTitle, pub description: TicketDescription, pub status: Status, } #[derive(Clone, Debug, PartialEq)] pub struct TicketDraft { pub title: TicketTitle, pub description: TicketDescription, } #[derive(Clone, Debug, Copy, PartialEq)] pub enum Status { ToDo, InProgress, Done, } impl TicketStore { pub fn new() -> Self { Self { tickets: Vec::new(), } } pub fn add_ticket(&mut self, ticket: Ticket) { self.tickets.push(ticket); } } #[cfg(test)] mod tests { use crate::{Status, TicketDraft, TicketStore}; use ticket_fields::test_helpers::{ticket_description, ticket_title}; #[test] fn works() { let mut store = TicketStore::new(); let draft1 = TicketDraft { title: ticket_title(), description: ticket_description(), }; let id1 = store.add_ticket(draft1.clone()); let ticket1 = store.get(id1).unwrap(); assert_eq!(draft1.title, ticket1.title); assert_eq!(draft1.description, ticket1.description); assert_eq!(ticket1.status, Status::ToDo); let draft2 = TicketDraft { title: ticket_title(), description: ticket_description(), }; let id2 = store.add_ticket(draft2); let ticket2 = store.get(id2).unwrap(); assert_ne!(id1, id2); } } ================================================ FILE: exercises/06_ticket_management/13_index/Cargo.toml ================================================ [package] name = "index" version = "0.1.0" edition = "2021" [dependencies] ticket_fields = { path = "../../../helpers/ticket_fields" } ================================================ FILE: exercises/06_ticket_management/13_index/src/lib.rs ================================================ // TODO: Implement `Index<&TicketId>` and `Index` for `TicketStore`. use ticket_fields::{TicketDescription, TicketTitle}; #[derive(Clone)] pub struct TicketStore { tickets: Vec, counter: u64, } #[derive(Clone, Copy, Debug, PartialEq)] pub struct TicketId(u64); #[derive(Clone, Debug, PartialEq)] pub struct Ticket { pub id: TicketId, pub title: TicketTitle, pub description: TicketDescription, pub status: Status, } #[derive(Clone, Debug, PartialEq)] pub struct TicketDraft { pub title: TicketTitle, pub description: TicketDescription, } #[derive(Clone, Debug, Copy, PartialEq)] pub enum Status { ToDo, InProgress, Done, } impl TicketStore { pub fn new() -> Self { Self { tickets: Vec::new(), counter: 0, } } pub fn add_ticket(&mut self, ticket: TicketDraft) -> TicketId { let id = TicketId(self.counter); self.counter += 1; let ticket = Ticket { id, title: ticket.title, description: ticket.description, status: Status::ToDo, }; self.tickets.push(ticket); id } pub fn get(&self, id: TicketId) -> Option<&Ticket> { self.tickets.iter().find(|&t| t.id == id) } } #[cfg(test)] mod tests { use crate::{Status, TicketDraft, TicketStore}; use ticket_fields::test_helpers::{ticket_description, ticket_title}; #[test] fn works() { let mut store = TicketStore::new(); let draft1 = TicketDraft { title: ticket_title(), description: ticket_description(), }; let id1 = store.add_ticket(draft1.clone()); let ticket1 = &store[id1]; assert_eq!(draft1.title, ticket1.title); assert_eq!(draft1.description, ticket1.description); assert_eq!(ticket1.status, Status::ToDo); let draft2 = TicketDraft { title: ticket_title(), description: ticket_description(), }; let id2 = store.add_ticket(draft2); let ticket2 = &store[&id2]; assert_ne!(id1, id2); } } ================================================ FILE: exercises/06_ticket_management/14_index_mut/Cargo.toml ================================================ [package] name = "index_mut" version = "0.1.0" edition = "2021" [dependencies] ticket_fields = { path = "../../../helpers/ticket_fields" } ================================================ FILE: exercises/06_ticket_management/14_index_mut/src/lib.rs ================================================ // TODO: Implement `IndexMut<&TicketId>` and `IndexMut` for `TicketStore`. use std::ops::Index; use ticket_fields::{TicketDescription, TicketTitle}; #[derive(Clone)] pub struct TicketStore { tickets: Vec, counter: u64, } #[derive(Clone, Copy, Debug, PartialEq)] pub struct TicketId(u64); #[derive(Clone, Debug, PartialEq)] pub struct Ticket { pub id: TicketId, pub title: TicketTitle, pub description: TicketDescription, pub status: Status, } #[derive(Clone, Debug, PartialEq)] pub struct TicketDraft { pub title: TicketTitle, pub description: TicketDescription, } #[derive(Clone, Debug, Copy, PartialEq)] pub enum Status { ToDo, InProgress, Done, } impl TicketStore { pub fn new() -> Self { Self { tickets: Vec::new(), counter: 0, } } pub fn add_ticket(&mut self, ticket: TicketDraft) -> TicketId { let id = TicketId(self.counter); self.counter += 1; let ticket = Ticket { id, title: ticket.title, description: ticket.description, status: Status::ToDo, }; self.tickets.push(ticket); id } pub fn get(&self, id: TicketId) -> Option<&Ticket> { self.tickets.iter().find(|&t| t.id == id) } } impl Index for TicketStore { type Output = Ticket; fn index(&self, index: TicketId) -> &Self::Output { self.get(index).unwrap() } } impl Index<&TicketId> for TicketStore { type Output = Ticket; fn index(&self, index: &TicketId) -> &Self::Output { &self[*index] } } #[cfg(test)] mod tests { use crate::{Status, TicketDraft, TicketStore}; use ticket_fields::test_helpers::{ticket_description, ticket_title}; #[test] fn works() { let mut store = TicketStore::new(); let draft = TicketDraft { title: ticket_title(), description: ticket_description(), }; let id = store.add_ticket(draft.clone()); let ticket = &store[id]; assert_eq!(draft.title, ticket.title); assert_eq!(draft.description, ticket.description); assert_eq!(ticket.status, Status::ToDo); let ticket = &mut store[id]; ticket.status = Status::InProgress; let ticket = &store[id]; assert_eq!(ticket.status, Status::InProgress); let ticket = &mut store[&id]; ticket.status = Status::Done; let ticket = &store[id]; assert_eq!(ticket.status, Status::Done); } } ================================================ FILE: exercises/06_ticket_management/15_hashmap/Cargo.toml ================================================ [package] name = "hashmap" version = "0.1.0" edition = "2021" [dependencies] ticket_fields = { path = "../../../helpers/ticket_fields" } ================================================ FILE: exercises/06_ticket_management/15_hashmap/src/lib.rs ================================================ // TODO: Replace `todo!()`s with the correct implementation. // Implement additional traits on `TicketId` if needed. use std::collections::HashMap; use std::ops::{Index, IndexMut}; use ticket_fields::{TicketDescription, TicketTitle}; #[derive(Clone)] pub struct TicketStore { tickets: HashMap, counter: u64, } #[derive(Clone, Copy, Debug, PartialEq)] pub struct TicketId(u64); #[derive(Clone, Debug, PartialEq)] pub struct Ticket { pub id: TicketId, pub title: TicketTitle, pub description: TicketDescription, pub status: Status, } #[derive(Clone, Debug, PartialEq, Eq)] pub struct TicketDraft { pub title: TicketTitle, pub description: TicketDescription, } #[derive(Clone, Debug, Copy, PartialEq, Eq)] pub enum Status { ToDo, InProgress, Done, } impl TicketStore { pub fn new() -> Self { Self { tickets: todo!(), counter: 0, } } pub fn add_ticket(&mut self, ticket: TicketDraft) -> TicketId { let id = TicketId(self.counter); self.counter += 1; let ticket = Ticket { id, title: ticket.title, description: ticket.description, status: Status::ToDo, }; todo!(); id } pub fn get(&self, id: TicketId) -> Option<&Ticket> { todo!() } pub fn get_mut(&mut self, id: TicketId) -> Option<&mut Ticket> { todo!() } } impl Index for TicketStore { type Output = Ticket; fn index(&self, index: TicketId) -> &Self::Output { self.get(index).unwrap() } } impl Index<&TicketId> for TicketStore { type Output = Ticket; fn index(&self, index: &TicketId) -> &Self::Output { &self[*index] } } impl IndexMut for TicketStore { fn index_mut(&mut self, index: TicketId) -> &mut Self::Output { self.get_mut(index).unwrap() } } impl IndexMut<&TicketId> for TicketStore { fn index_mut(&mut self, index: &TicketId) -> &mut Self::Output { &mut self[*index] } } #[cfg(test)] mod tests { use crate::{Status, TicketDraft, TicketStore}; use ticket_fields::test_helpers::{ticket_description, ticket_title}; #[test] fn works() { let mut store = TicketStore::new(); let draft = TicketDraft { title: ticket_title(), description: ticket_description(), }; let id = store.add_ticket(draft.clone()); let ticket = &store[id]; assert_eq!(draft.title, ticket.title); assert_eq!(draft.description, ticket.description); assert_eq!(ticket.status, Status::ToDo); let ticket = &mut store[id]; ticket.status = Status::InProgress; let ticket = &store[id]; assert_eq!(ticket.status, Status::InProgress); } } ================================================ FILE: exercises/06_ticket_management/16_btreemap/Cargo.toml ================================================ [package] name = "btreemap" version = "0.1.0" edition = "2021" [dependencies] ticket_fields = { path = "../../../helpers/ticket_fields" } ================================================ FILE: exercises/06_ticket_management/16_btreemap/src/lib.rs ================================================ // TODO: Replace `todo!()`s with the correct implementation. // Implement `IntoIterator` for `&TicketStore`. The iterator should yield immutable // references to the tickets, ordered by their `TicketId`. // Implement additional traits on `TicketId` if needed. use std::collections::BTreeMap; use std::ops::{Index, IndexMut}; use ticket_fields::{TicketDescription, TicketTitle}; #[derive(Clone)] pub struct TicketStore { tickets: BTreeMap, counter: u64, } #[derive(Clone, Copy, Debug, PartialEq)] pub struct TicketId(u64); #[derive(Clone, Debug, PartialEq)] pub struct Ticket { pub id: TicketId, pub title: TicketTitle, pub description: TicketDescription, pub status: Status, } #[derive(Clone, Debug, PartialEq, Eq)] pub struct TicketDraft { pub title: TicketTitle, pub description: TicketDescription, } #[derive(Clone, Debug, Copy, PartialEq, Eq)] pub enum Status { ToDo, InProgress, Done, } impl TicketStore { pub fn new() -> Self { Self { tickets: todo!(), counter: 0, } } pub fn add_ticket(&mut self, ticket: TicketDraft) -> TicketId { let id = TicketId(self.counter); self.counter += 1; let ticket = Ticket { id, title: ticket.title, description: ticket.description, status: Status::ToDo, }; todo!(); id } pub fn get(&self, id: TicketId) -> Option<&Ticket> { todo!() } pub fn get_mut(&mut self, id: TicketId) -> Option<&mut Ticket> { todo!() } } impl Index for TicketStore { type Output = Ticket; fn index(&self, index: TicketId) -> &Self::Output { self.get(index).unwrap() } } impl Index<&TicketId> for TicketStore { type Output = Ticket; fn index(&self, index: &TicketId) -> &Self::Output { &self[*index] } } impl IndexMut for TicketStore { fn index_mut(&mut self, index: TicketId) -> &mut Self::Output { self.get_mut(index).unwrap() } } impl IndexMut<&TicketId> for TicketStore { fn index_mut(&mut self, index: &TicketId) -> &mut Self::Output { &mut self[*index] } } #[cfg(test)] mod tests { use crate::{Status, TicketDraft, TicketId, TicketStore}; use ticket_fields::test_helpers::{ticket_description, ticket_title}; #[test] fn works() { let mut store = TicketStore::new(); let n_tickets = 5; for i in 0..n_tickets { let draft = TicketDraft { title: ticket_title(), description: ticket_description(), }; let id = store.add_ticket(draft.clone()); let ticket = &store[id]; assert_eq!(draft.title, ticket.title); assert_eq!(draft.description, ticket.description); assert_eq!(ticket.status, Status::ToDo); let ticket = &mut store[id]; ticket.status = Status::InProgress; let ticket = &store[id]; assert_eq!(ticket.status, Status::InProgress); } let ids: Vec = (&store).into_iter().map(|t| t.id).collect(); let sorted_ids = { let mut v = ids.clone(); v.sort(); v }; assert_eq!(ids, sorted_ids); } } ================================================ FILE: exercises/07_threads/00_intro/Cargo.toml ================================================ [package] name = "intro_07" version = "0.1.0" edition = "2021" ================================================ FILE: exercises/07_threads/00_intro/src/lib.rs ================================================ fn intro() -> &'static str { // TODO: fix me 👇 "I'm ready to _!" } #[cfg(test)] mod tests { use crate::intro; #[test] fn test_intro() { assert_eq!( intro(), "I'm ready to build a concurrent ticket management system!" ); } } ================================================ FILE: exercises/07_threads/01_threads/Cargo.toml ================================================ [package] name = "threads" version = "0.1.0" edition = "2021" ================================================ FILE: exercises/07_threads/01_threads/src/lib.rs ================================================ // TODO: implement a multi-threaded version of the `sum` function // using `spawn` and `join`. // Given a vector of integers, split the vector into two halves and // sum each half in a separate thread. // Caveat: We can't test *how* the function is implemented, // we can only verify that it produces the correct result. // You _could_ pass this test by just returning `v.iter().sum()`, // but that would defeat the purpose of the exercise. // // Hint: you won't be able to get the spawned threads to _borrow_ // slices of the vector directly. You'll need to allocate new // vectors for each half of the original vector. We'll see why // this is necessary in the next exercise. use std::thread; pub fn sum(v: Vec) -> i32 { todo!() } #[cfg(test)] mod tests { use super::*; #[test] fn empty() { assert_eq!(sum(vec![]), 0); } #[test] fn one() { assert_eq!(sum(vec![1]), 1); } #[test] fn five() { assert_eq!(sum(vec![1, 2, 3, 4, 5]), 15); } #[test] fn nine() { assert_eq!(sum(vec![1, 2, 3, 4, 5, 6, 7, 8, 9]), 45); } #[test] fn ten() { assert_eq!(sum(vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10]), 55); } } ================================================ FILE: exercises/07_threads/02_static/Cargo.toml ================================================ [package] name = "static" version = "0.1.0" edition = "2021" ================================================ FILE: exercises/07_threads/02_static/src/lib.rs ================================================ // TODO: Given a static slice of integers, split the slice into two halves and // sum each half in a separate thread. // Do not allocate any additional memory! use std::thread; pub fn sum(slice: &'static [i32]) -> i32 { todo!() } #[cfg(test)] mod tests { use super::*; #[test] fn empty() { static ARRAY: [i32; 0] = []; assert_eq!(sum(&ARRAY), 0); } #[test] fn one() { static ARRAY: [i32; 1] = [1]; assert_eq!(sum(&ARRAY), 1); } #[test] fn five() { static ARRAY: [i32; 5] = [1, 2, 3, 4, 5]; assert_eq!(sum(&ARRAY), 15); } #[test] fn nine() { static ARRAY: [i32; 9] = [1, 2, 3, 4, 5, 6, 7, 8, 9]; assert_eq!(sum(&ARRAY), 45); } #[test] fn ten() { static ARRAY: [i32; 10] = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; assert_eq!(sum(&ARRAY), 55); } } ================================================ FILE: exercises/07_threads/03_leak/Cargo.toml ================================================ [package] name = "leaking" version = "0.1.0" edition = "2021" ================================================ FILE: exercises/07_threads/03_leak/src/lib.rs ================================================ // TODO: Given a vector of integers, leak its heap allocation. // Then split the resulting static slice into two halves and // sum each half in a separate thread. // Hint: check out `Vec::leak`. use std::thread; pub fn sum(v: Vec) -> i32 { todo!() } #[cfg(test)] mod tests { use super::*; #[test] fn empty() { assert_eq!(sum(vec![]), 0); } #[test] fn one() { assert_eq!(sum(vec![1]), 1); } #[test] fn five() { assert_eq!(sum(vec![1, 2, 3, 4, 5]), 15); } #[test] fn nine() { assert_eq!(sum(vec![1, 2, 3, 4, 5, 6, 7, 8, 9]), 45); } #[test] fn ten() { assert_eq!(sum(vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10]), 55); } } ================================================ FILE: exercises/07_threads/04_scoped_threads/Cargo.toml ================================================ [package] name = "scoped_threads" version = "0.1.0" edition = "2021" ================================================ FILE: exercises/07_threads/04_scoped_threads/src/lib.rs ================================================ // TODO: Given a vector of integers, split it in two halves // and compute the sum of each half in a separate thread. // Don't perform any heap allocation. Don't leak any memory. pub fn sum(v: Vec) -> i32 { todo!() } #[cfg(test)] mod tests { use super::*; #[test] fn empty() { assert_eq!(sum(vec![]), 0); } #[test] fn one() { assert_eq!(sum(vec![1]), 1); } #[test] fn five() { assert_eq!(sum(vec![1, 2, 3, 4, 5]), 15); } #[test] fn nine() { assert_eq!(sum(vec![1, 2, 3, 4, 5, 6, 7, 8, 9]), 45); } #[test] fn ten() { assert_eq!(sum(vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10]), 55); } } ================================================ FILE: exercises/07_threads/05_channels/Cargo.toml ================================================ [package] name = "channels" version = "0.1.0" edition = "2021" [dependencies] ticket_fields = { path = "../../../helpers/ticket_fields" } ================================================ FILE: exercises/07_threads/05_channels/src/data.rs ================================================ use crate::store::TicketId; use ticket_fields::{TicketDescription, TicketTitle}; #[derive(Clone, Debug, PartialEq)] pub struct Ticket { pub id: TicketId, pub title: TicketTitle, pub description: TicketDescription, pub status: Status, } #[derive(Clone, Debug, PartialEq, Eq)] pub struct TicketDraft { pub title: TicketTitle, pub description: TicketDescription, } #[derive(Clone, Debug, Copy, PartialEq, Eq)] pub enum Status { ToDo, InProgress, Done, } ================================================ FILE: exercises/07_threads/05_channels/src/lib.rs ================================================ use std::sync::mpsc::{Receiver, Sender}; pub mod data; pub mod store; pub enum Command { Insert(todo!()), } // Start the system by spawning the server thread. // It returns a `Sender` instance which can then be used // by one or more clients to interact with the server. pub fn launch() -> Sender { let (sender, receiver) = std::sync::mpsc::channel(); std::thread::spawn(move || server(receiver)); sender } // TODO: The server task should **never** stop. // Enter a loop: wait for a command to show up in // the channel, then execute it, then start waiting // for the next command. pub fn server(receiver: Receiver) {} ================================================ FILE: exercises/07_threads/05_channels/src/store.rs ================================================ use crate::data::{Status, Ticket, TicketDraft}; use std::collections::BTreeMap; #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] pub struct TicketId(u64); #[derive(Clone)] pub struct TicketStore { tickets: BTreeMap, counter: u64, } impl TicketStore { pub fn new() -> Self { Self { tickets: BTreeMap::new(), counter: 0, } } pub fn add_ticket(&mut self, ticket: TicketDraft) -> TicketId { let id = TicketId(self.counter); self.counter += 1; let ticket = Ticket { id, title: ticket.title, description: ticket.description, status: Status::ToDo, }; self.tickets.insert(id, ticket); id } } ================================================ FILE: exercises/07_threads/05_channels/tests/insert.rs ================================================ // TODO: Set `move_forward` to `true` in `ready` when you think you're done with this exercise. // Feel free to call an instructor to verify your solution! use channels::data::TicketDraft; use channels::{launch, Command}; use std::time::Duration; use ticket_fields::test_helpers::{ticket_description, ticket_title}; #[test] fn a_thread_is_spawned() { let sender = launch(); std::thread::sleep(Duration::from_millis(200)); sender .send(Command::Insert(TicketDraft { title: ticket_title(), description: ticket_description(), })) // If the thread is no longer running, this will panic // because the channel will be closed. .expect("Did you actually spawn a thread? The channel is closed!"); } #[test] fn ready() { // There's very little that we can check automatically in this exercise, // since our server doesn't expose any **read** actions. // We have no way to know if the inserts are actually happening and if they // are happening correctly. let move_forward = false; assert!(move_forward); } ================================================ FILE: exercises/07_threads/06_interior_mutability/Cargo.toml ================================================ [package] name = "interior_mutability" version = "0.1.0" edition = "2021" ================================================ FILE: exercises/07_threads/06_interior_mutability/src/lib.rs ================================================ // TODO: Use `Rc` and `RefCell` to implement `DropTracker`, a wrapper around a value of type `T` // that increments a shared `usize` counter every time the wrapped value is dropped. use std::cell::RefCell; use std::rc::Rc; pub struct DropTracker { value: T, counter: todo!(), } impl DropTracker { pub fn new(value: T, counter: todo!()) -> Self { Self { value, counter } } } impl Drop for DropTracker { fn drop(&mut self) { todo!() } } #[cfg(test)] mod tests { use super::*; #[test] fn it_works() { let counter = Rc::new(RefCell::new(0)); let _ = DropTracker::new((), Rc::clone(&counter)); assert_eq!(*counter.borrow(), 1); } #[test] fn multiple() { let counter = Rc::new(RefCell::new(0)); { let a = DropTracker::new(5, Rc::clone(&counter)); let b = DropTracker::new(6, Rc::clone(&counter)); } assert_eq!(*counter.borrow(), 2); } } ================================================ FILE: exercises/07_threads/07_ack/Cargo.toml ================================================ [package] name = "response" version = "0.1.0" edition = "2021" [dependencies] ticket_fields = { path = "../../../helpers/ticket_fields" } ================================================ FILE: exercises/07_threads/07_ack/src/data.rs ================================================ use crate::store::TicketId; use ticket_fields::{TicketDescription, TicketTitle}; #[derive(Clone, Debug, PartialEq)] pub struct Ticket { pub id: TicketId, pub title: TicketTitle, pub description: TicketDescription, pub status: Status, } #[derive(Clone, Debug, PartialEq, Eq)] pub struct TicketDraft { pub title: TicketTitle, pub description: TicketDescription, } #[derive(Clone, Debug, Copy, PartialEq, Eq)] pub enum Status { ToDo, InProgress, Done, } ================================================ FILE: exercises/07_threads/07_ack/src/lib.rs ================================================ use std::sync::mpsc::{Receiver, Sender}; use crate::store::TicketStore; pub mod data; pub mod store; // Refer to the tests to understand the expected schema. pub enum Command { Insert { todo!() }, Get { todo!() } } pub fn launch() -> Sender { let (sender, receiver) = std::sync::mpsc::channel(); std::thread::spawn(move || server(receiver)); sender } // TODO: handle incoming commands as expected. pub fn server(receiver: Receiver) { let mut store = TicketStore::new(); loop { match receiver.recv() { Ok(Command::Insert {}) => { todo!() } Ok(Command::Get { todo!() }) => { todo!() } Err(_) => { // There are no more senders, so we can safely break // and shut down the server. break }, } } } ================================================ FILE: exercises/07_threads/07_ack/src/store.rs ================================================ use crate::data::{Status, Ticket, TicketDraft}; use std::collections::BTreeMap; #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] pub struct TicketId(u64); #[derive(Clone)] pub struct TicketStore { tickets: BTreeMap, counter: u64, } impl TicketStore { pub fn new() -> Self { Self { tickets: BTreeMap::new(), counter: 0, } } pub fn add_ticket(&mut self, ticket: TicketDraft) -> TicketId { let id = TicketId(self.counter); self.counter += 1; let ticket = Ticket { id, title: ticket.title, description: ticket.description, status: Status::ToDo, }; self.tickets.insert(id, ticket); id } pub fn get(&self, id: TicketId) -> Option<&Ticket> { self.tickets.get(&id) } } ================================================ FILE: exercises/07_threads/07_ack/tests/insert.rs ================================================ use response::data::{Status, Ticket, TicketDraft}; use response::store::TicketId; use response::{launch, Command}; use ticket_fields::test_helpers::{ticket_description, ticket_title}; #[test] fn insert_works() { let sender = launch(); let (response_sender, response_receiver) = std::sync::mpsc::channel(); let draft = TicketDraft { title: ticket_title(), description: ticket_description(), }; let command = Command::Insert { draft: draft.clone(), response_sender, }; sender .send(command) // If the thread is no longer running, this will panic // because the channel will be closed. .expect("Did you actually spawn a thread? The channel is closed!"); let ticket_id: TicketId = response_receiver.recv().expect("No response received!"); let (response_sender, response_receiver) = std::sync::mpsc::channel(); let command = Command::Get { id: ticket_id, response_sender, }; sender .send(command) .expect("Did you actually spawn a thread? The channel is closed!"); let ticket: Ticket = response_receiver .recv() .expect("No response received!") .unwrap(); assert_eq!(ticket_id, ticket.id); assert_eq!(ticket.status, Status::ToDo); assert_eq!(ticket.title, draft.title); assert_eq!(ticket.description, draft.description); } ================================================ FILE: exercises/07_threads/08_client/Cargo.toml ================================================ [package] name = "client" version = "0.1.0" edition = "2021" [dependencies] ticket_fields = { path = "../../../helpers/ticket_fields" } ================================================ FILE: exercises/07_threads/08_client/src/data.rs ================================================ use crate::store::TicketId; use ticket_fields::{TicketDescription, TicketTitle}; #[derive(Clone, Debug, PartialEq)] pub struct Ticket { pub id: TicketId, pub title: TicketTitle, pub description: TicketDescription, pub status: Status, } #[derive(Clone, Debug, PartialEq, Eq)] pub struct TicketDraft { pub title: TicketTitle, pub description: TicketDescription, } #[derive(Clone, Debug, Copy, PartialEq, Eq)] pub enum Status { ToDo, InProgress, Done, } ================================================ FILE: exercises/07_threads/08_client/src/lib.rs ================================================ use crate::data::{Ticket, TicketDraft}; use crate::store::{TicketId, TicketStore}; use std::sync::mpsc::{Receiver, Sender}; pub mod data; pub mod store; #[derive(Clone)] // TODO: flesh out the client implementation. pub struct TicketStoreClient {} impl TicketStoreClient { // Feel free to panic on all errors, for simplicity. pub fn insert(&self, draft: TicketDraft) -> TicketId { todo!() } pub fn get(&self, id: TicketId) -> Option { todo!() } } pub fn launch() -> TicketStoreClient { let (sender, receiver) = std::sync::mpsc::channel(); std::thread::spawn(move || server(receiver)); todo!() } // No longer public! This becomes an internal detail of the library now. enum Command { Insert { draft: TicketDraft, response_channel: Sender, }, Get { id: TicketId, response_channel: Sender>, }, } fn server(receiver: Receiver) { let mut store = TicketStore::new(); loop { match receiver.recv() { Ok(Command::Insert { draft, response_channel, }) => { let id = store.add_ticket(draft); let _ = response_channel.send(id); } Ok(Command::Get { id, response_channel, }) => { let ticket = store.get(id); let _ = response_channel.send(ticket.cloned()); } Err(_) => { // There are no more senders, so we can safely break // and shut down the server. break; } } } } ================================================ FILE: exercises/07_threads/08_client/src/store.rs ================================================ use crate::data::{Status, Ticket, TicketDraft}; use std::collections::BTreeMap; #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] pub struct TicketId(u64); #[derive(Clone)] pub struct TicketStore { tickets: BTreeMap, counter: u64, } impl TicketStore { pub fn new() -> Self { Self { tickets: BTreeMap::new(), counter: 0, } } pub fn add_ticket(&mut self, ticket: TicketDraft) -> TicketId { let id = TicketId(self.counter); self.counter += 1; let ticket = Ticket { id, title: ticket.title, description: ticket.description, status: Status::ToDo, }; self.tickets.insert(id, ticket); id } pub fn get(&self, id: TicketId) -> Option<&Ticket> { self.tickets.get(&id) } } ================================================ FILE: exercises/07_threads/08_client/tests/insert.rs ================================================ use client::data::{Status, TicketDraft}; use client::launch; use ticket_fields::test_helpers::{ticket_description, ticket_title}; #[test] fn insert_works() { // Notice how much simpler the test is now that we have a client to handle the details! let client = launch(); let draft = TicketDraft { title: ticket_title(), description: ticket_description(), }; let ticket_id = client.insert(draft.clone()); let client2 = client.clone(); let ticket = client2.get(ticket_id).unwrap(); assert_eq!(ticket_id, ticket.id); assert_eq!(ticket.status, Status::ToDo); assert_eq!(ticket.title, draft.title); assert_eq!(ticket.description, draft.description); } ================================================ FILE: exercises/07_threads/09_bounded/Cargo.toml ================================================ [package] name = "bounded" version = "0.1.0" edition = "2021" [dependencies] ticket_fields = { path = "../../../helpers/ticket_fields" } ================================================ FILE: exercises/07_threads/09_bounded/src/data.rs ================================================ use crate::store::TicketId; use ticket_fields::{TicketDescription, TicketTitle}; #[derive(Clone, Debug, PartialEq)] pub struct Ticket { pub id: TicketId, pub title: TicketTitle, pub description: TicketDescription, pub status: Status, } #[derive(Clone, Debug, PartialEq, Eq)] pub struct TicketDraft { pub title: TicketTitle, pub description: TicketDescription, } #[derive(Clone, Debug, Copy, PartialEq, Eq)] pub enum Status { ToDo, InProgress, Done, } ================================================ FILE: exercises/07_threads/09_bounded/src/lib.rs ================================================ // TODO: Convert the implementation to use bounded channels. use crate::data::{Ticket, TicketDraft}; use crate::store::{TicketId, TicketStore}; use std::sync::mpsc::{Receiver, Sender}; pub mod data; pub mod store; #[derive(Clone)] pub struct TicketStoreClient { sender: todo!(), } impl TicketStoreClient { pub fn insert(&self, draft: TicketDraft) -> Result { todo!() } pub fn get(&self, id: TicketId) -> Result, todo!()> { todo!() } } pub fn launch(capacity: usize) -> TicketStoreClient { todo!(); std::thread::spawn(move || server(receiver)); todo!() } enum Command { Insert { draft: TicketDraft, response_channel: todo!(), }, Get { id: TicketId, response_channel: todo!(), }, } pub fn server(receiver: Receiver) { let mut store = TicketStore::new(); loop { match receiver.recv() { Ok(Command::Insert { draft, response_channel, }) => { let id = store.add_ticket(draft); todo!() } Ok(Command::Get { id, response_channel, }) => { let ticket = store.get(id); todo!() } Err(_) => { // There are no more senders, so we can safely break // and shut down the server. break; } } } } ================================================ FILE: exercises/07_threads/09_bounded/src/store.rs ================================================ use crate::data::{Status, Ticket, TicketDraft}; use std::collections::BTreeMap; #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] pub struct TicketId(u64); #[derive(Clone)] pub struct TicketStore { tickets: BTreeMap, counter: u64, } impl TicketStore { pub fn new() -> Self { Self { tickets: BTreeMap::new(), counter: 0, } } pub fn add_ticket(&mut self, ticket: TicketDraft) -> TicketId { let id = TicketId(self.counter); self.counter += 1; let ticket = Ticket { id, title: ticket.title, description: ticket.description, status: Status::ToDo, }; self.tickets.insert(id, ticket); id } pub fn get(&self, id: TicketId) -> Option<&Ticket> { self.tickets.get(&id) } } ================================================ FILE: exercises/07_threads/09_bounded/tests/insert.rs ================================================ use bounded::data::{Status, TicketDraft}; use bounded::launch; use ticket_fields::test_helpers::{ticket_description, ticket_title}; #[test] fn works() { let client = launch(5); let draft = TicketDraft { title: ticket_title(), description: ticket_description(), }; let ticket_id = client.insert(draft.clone()).unwrap(); let client2 = client.clone(); let ticket = client2.get(ticket_id).unwrap().unwrap(); assert_eq!(ticket_id, ticket.id); assert_eq!(ticket.status, Status::ToDo); assert_eq!(ticket.title, draft.title); assert_eq!(ticket.description, draft.description); } ================================================ FILE: exercises/07_threads/10_patch/Cargo.toml ================================================ [package] name = "patch" version = "0.1.0" edition = "2021" [dependencies] thiserror = "1.0.69" ticket_fields = { path = "../../../helpers/ticket_fields" } ================================================ FILE: exercises/07_threads/10_patch/src/data.rs ================================================ use crate::store::TicketId; use ticket_fields::{TicketDescription, TicketTitle}; #[derive(Clone, Debug, PartialEq)] pub struct Ticket { pub id: TicketId, pub title: TicketTitle, pub description: TicketDescription, pub status: Status, } #[derive(Clone, Debug, PartialEq, Eq)] pub struct TicketDraft { pub title: TicketTitle, pub description: TicketDescription, } #[derive(Clone, Debug, PartialEq, Eq)] pub struct TicketPatch { pub id: TicketId, pub title: Option, pub description: Option, pub status: Option, } #[derive(Clone, Debug, Copy, PartialEq, Eq)] pub enum Status { ToDo, InProgress, Done, } ================================================ FILE: exercises/07_threads/10_patch/src/lib.rs ================================================ use std::sync::mpsc::{sync_channel, Receiver, SyncSender}; // TODO: Implement the patching functionality. use crate::data::{Ticket, TicketDraft, TicketPatch}; use crate::store::{TicketId, TicketStore}; pub mod data; pub mod store; #[derive(Clone)] pub struct TicketStoreClient { sender: SyncSender, } impl TicketStoreClient { pub fn insert(&self, draft: TicketDraft) -> Result { let (response_sender, response_receiver) = sync_channel(1); self.sender .try_send(Command::Insert { draft, response_channel: response_sender, }) .map_err(|_| OverloadedError)?; Ok(response_receiver.recv().unwrap()) } pub fn get(&self, id: TicketId) -> Result, OverloadedError> { let (response_sender, response_receiver) = sync_channel(1); self.sender .try_send(Command::Get { id, response_channel: response_sender, }) .map_err(|_| OverloadedError)?; Ok(response_receiver.recv().unwrap()) } pub fn update(&self, ticket_patch: TicketPatch) -> Result<(), OverloadedError> {} } #[derive(Debug, thiserror::Error)] #[error("The store is overloaded")] pub struct OverloadedError; pub fn launch(capacity: usize) -> TicketStoreClient { let (sender, receiver) = sync_channel(capacity); std::thread::spawn(move || server(receiver)); TicketStoreClient { sender } } enum Command { Insert { draft: TicketDraft, response_channel: SyncSender, }, Get { id: TicketId, response_channel: SyncSender>, }, Update { patch: TicketPatch, response_channel: SyncSender<()>, }, } pub fn server(receiver: Receiver) { let mut store = TicketStore::new(); loop { match receiver.recv() { Ok(Command::Insert { draft, response_channel, }) => { let id = store.add_ticket(draft); let _ = response_channel.send(id); } Ok(Command::Get { id, response_channel, }) => { let ticket = store.get(id); let _ = response_channel.send(ticket.cloned()); } Ok(Command::Update { patch, response_channel, }) => { todo!() } Err(_) => { // There are no more senders, so we can safely break // and shut down the server. break; } } } } ================================================ FILE: exercises/07_threads/10_patch/src/store.rs ================================================ use crate::data::{Status, Ticket, TicketDraft}; use std::collections::BTreeMap; #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] pub struct TicketId(u64); #[derive(Clone)] pub struct TicketStore { tickets: BTreeMap, counter: u64, } impl TicketStore { pub fn new() -> Self { Self { tickets: BTreeMap::new(), counter: 0, } } pub fn add_ticket(&mut self, ticket: TicketDraft) -> TicketId { let id = TicketId(self.counter); self.counter += 1; let ticket = Ticket { id, title: ticket.title, description: ticket.description, status: Status::ToDo, }; self.tickets.insert(id, ticket); id } pub fn get(&self, id: TicketId) -> Option<&Ticket> { self.tickets.get(&id) } pub fn get_mut(&mut self, id: TicketId) -> Option<&mut Ticket> { self.tickets.get_mut(&id) } } ================================================ FILE: exercises/07_threads/10_patch/tests/check.rs ================================================ use patch::data::{Status, TicketDraft, TicketPatch}; use patch::launch; use ticket_fields::test_helpers::{ticket_description, ticket_title}; #[test] fn works() { let client = launch(5); let draft = TicketDraft { title: ticket_title(), description: ticket_description(), }; let ticket_id = client.insert(draft.clone()).unwrap(); let ticket = client.get(ticket_id).unwrap().unwrap(); assert_eq!(ticket_id, ticket.id); assert_eq!(ticket.status, Status::ToDo); assert_eq!(ticket.title, draft.title); assert_eq!(ticket.description, draft.description); let patch = TicketPatch { id: ticket_id, title: None, description: None, status: Some(Status::InProgress), }; client.update(patch).unwrap(); let ticket = client.get(ticket_id).unwrap().unwrap(); assert_eq!(ticket.id, ticket_id); assert_eq!(ticket.status, Status::InProgress); } ================================================ FILE: exercises/07_threads/11_locks/Cargo.toml ================================================ [package] name = "locks" version = "0.1.0" edition = "2021" [dependencies] thiserror = "1.0.69" ticket_fields = { path = "../../../helpers/ticket_fields" } ================================================ FILE: exercises/07_threads/11_locks/src/data.rs ================================================ use crate::store::TicketId; use ticket_fields::{TicketDescription, TicketTitle}; #[derive(Clone, Debug, PartialEq)] pub struct Ticket { pub id: TicketId, pub title: TicketTitle, pub description: TicketDescription, pub status: Status, } #[derive(Clone, Debug, PartialEq, Eq)] pub struct TicketDraft { pub title: TicketTitle, pub description: TicketDescription, } #[derive(Clone, Debug, Copy, PartialEq, Eq)] pub enum Status { ToDo, InProgress, Done, } ================================================ FILE: exercises/07_threads/11_locks/src/lib.rs ================================================ // TODO: Fill in the missing methods for `TicketStore`. // Notice how we no longer need a separate update command: `Get` now returns a handle to the ticket // which allows the caller to both modify and read the ticket. use std::sync::mpsc::{sync_channel, Receiver, SyncSender, TrySendError}; use std::sync::{Arc, Mutex}; use crate::data::{Ticket, TicketDraft}; use crate::store::{TicketId, TicketStore}; pub mod data; pub mod store; #[derive(Clone)] pub struct TicketStoreClient { sender: SyncSender, } impl TicketStoreClient { pub fn insert(&self, draft: TicketDraft) -> Result { let (response_sender, response_receiver) = sync_channel(1); self.sender .try_send(Command::Insert { draft, response_channel: response_sender, }) .map_err(|_| OverloadedError)?; Ok(response_receiver.recv().unwrap()) } pub fn get(&self, id: TicketId) -> Result>>, OverloadedError> { let (response_sender, response_receiver) = sync_channel(1); self.sender .try_send(Command::Get { id, response_channel: response_sender, }) .map_err(|_| OverloadedError)?; Ok(response_receiver.recv().unwrap()) } } #[derive(Debug, thiserror::Error)] #[error("The store is overloaded")] pub struct OverloadedError; pub fn launch(capacity: usize) -> TicketStoreClient { let (sender, receiver) = sync_channel(capacity); std::thread::spawn(move || server(receiver)); TicketStoreClient { sender } } enum Command { Insert { draft: TicketDraft, response_channel: SyncSender, }, Get { id: TicketId, response_channel: SyncSender>>>, }, } pub fn server(receiver: Receiver) { let mut store = TicketStore::new(); loop { match receiver.recv() { Ok(Command::Insert { draft, response_channel, }) => { let id = store.add_ticket(draft); let _ = response_channel.send(id); } Ok(Command::Get { id, response_channel, }) => { let ticket = store.get(id); let _ = response_channel.send(ticket); } Err(_) => { // There are no more senders, so we can safely break // and shut down the server. break; } } } } ================================================ FILE: exercises/07_threads/11_locks/src/store.rs ================================================ use crate::data::{Status, Ticket, TicketDraft}; use std::collections::BTreeMap; use std::sync::{Arc, Mutex}; #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] pub struct TicketId(u64); #[derive(Clone)] pub struct TicketStore { tickets: BTreeMap>>, counter: u64, } impl TicketStore { pub fn new() -> Self { Self { tickets: BTreeMap::new(), counter: 0, } } pub fn add_ticket(&mut self, ticket: TicketDraft) -> TicketId { let id = TicketId(self.counter); self.counter += 1; let ticket = Ticket { id, title: ticket.title, description: ticket.description, status: Status::ToDo, }; todo!(); id } // The `get` method should return a handle to the ticket // which allows the caller to either read or modify the ticket. pub fn get(&self, id: TicketId) -> Option { todo!() } } ================================================ FILE: exercises/07_threads/11_locks/tests/check.rs ================================================ use locks::data::{Status, TicketDraft}; use locks::launch; use ticket_fields::test_helpers::{ticket_description, ticket_title}; #[test] fn works() { let client = launch(5); let draft = TicketDraft { title: ticket_title(), description: ticket_description(), }; let ticket_id = client.insert(draft.clone()).unwrap(); let ticket = client.get(ticket_id).unwrap().unwrap(); { let mut ticket = ticket.lock().unwrap(); assert_eq!(ticket_id, ticket.id); assert_eq!(ticket.status, Status::ToDo); assert_eq!(ticket.title, draft.title); assert_eq!(ticket.description, draft.description); ticket.status = Status::InProgress; } let ticket = client.get(ticket_id).unwrap().unwrap(); { let ticket = ticket.lock().unwrap(); assert_eq!(ticket_id, ticket.id); assert_eq!(ticket.status, Status::InProgress); } } ================================================ FILE: exercises/07_threads/12_rw_lock/Cargo.toml ================================================ [package] name = "rwlock" version = "0.1.0" edition = "2021" [dependencies] thiserror = "1.0.69" ticket_fields = { path = "../../../helpers/ticket_fields" } ================================================ FILE: exercises/07_threads/12_rw_lock/src/data.rs ================================================ use crate::store::TicketId; use ticket_fields::{TicketDescription, TicketTitle}; #[derive(Clone, Debug, PartialEq)] pub struct Ticket { pub id: TicketId, pub title: TicketTitle, pub description: TicketDescription, pub status: Status, } #[derive(Clone, Debug, PartialEq, Eq)] pub struct TicketDraft { pub title: TicketTitle, pub description: TicketDescription, } #[derive(Clone, Debug, Copy, PartialEq, Eq)] pub enum Status { ToDo, InProgress, Done, } ================================================ FILE: exercises/07_threads/12_rw_lock/src/lib.rs ================================================ // TODO: Replace `Mutex` with `RwLock` in the `TicketStore` struct and // all other relevant places to allow multiple readers to access the ticket store concurrently. use std::sync::mpsc::{sync_channel, Receiver, SyncSender, TrySendError}; use std::sync::{Arc, Mutex}; use crate::data::{Ticket, TicketDraft}; use crate::store::{TicketId, TicketStore}; pub mod data; pub mod store; #[derive(Clone)] pub struct TicketStoreClient { sender: SyncSender, } impl TicketStoreClient { pub fn insert(&self, draft: TicketDraft) -> Result { let (response_sender, response_receiver) = sync_channel(1); self.sender .try_send(Command::Insert { draft, response_channel: response_sender, }) .map_err(|_| OverloadedError)?; Ok(response_receiver.recv().unwrap()) } pub fn get(&self, id: TicketId) -> Result>>, OverloadedError> { let (response_sender, response_receiver) = sync_channel(1); self.sender .try_send(Command::Get { id, response_channel: response_sender, }) .map_err(|_| OverloadedError)?; Ok(response_receiver.recv().unwrap()) } } #[derive(Debug, thiserror::Error)] #[error("The store is overloaded")] pub struct OverloadedError; pub fn launch(capacity: usize) -> TicketStoreClient { let (sender, receiver) = sync_channel(capacity); std::thread::spawn(move || server(receiver)); TicketStoreClient { sender } } enum Command { Insert { draft: TicketDraft, response_channel: SyncSender, }, Get { id: TicketId, response_channel: SyncSender>>>, }, } pub fn server(receiver: Receiver) { let mut store = TicketStore::new(); loop { match receiver.recv() { Ok(Command::Insert { draft, response_channel, }) => { let id = store.add_ticket(draft); let _ = response_channel.send(id); } Ok(Command::Get { id, response_channel, }) => { let ticket = store.get(id); let _ = response_channel.send(ticket); } Err(_) => { // There are no more senders, so we can safely break // and shut down the server. break; } } } } ================================================ FILE: exercises/07_threads/12_rw_lock/src/store.rs ================================================ use crate::data::{Status, Ticket, TicketDraft}; use std::collections::BTreeMap; use std::sync::{Arc, Mutex}; #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] pub struct TicketId(u64); #[derive(Clone)] pub struct TicketStore { tickets: BTreeMap>>, counter: u64, } impl TicketStore { pub fn new() -> Self { Self { tickets: BTreeMap::new(), counter: 0, } } pub fn add_ticket(&mut self, ticket: TicketDraft) -> TicketId { let id = TicketId(self.counter); self.counter += 1; let ticket = Ticket { id, title: ticket.title, description: ticket.description, status: Status::ToDo, }; let ticket = Arc::new(Mutex::new(ticket)); self.tickets.insert(id, ticket); id } // The `get` method should return a handle to the ticket // which allows the caller to either read or modify the ticket. pub fn get(&self, id: TicketId) -> Option>> { self.tickets.get(&id).cloned() } } ================================================ FILE: exercises/07_threads/12_rw_lock/tests/check.rs ================================================ use rwlock::data::{Status, TicketDraft}; use rwlock::launch; use ticket_fields::test_helpers::{ticket_description, ticket_title}; #[test] fn works() { let client = launch(5); let draft = TicketDraft { title: ticket_title(), description: ticket_description(), }; let ticket_id = client.insert(draft.clone()).unwrap(); let ticket = client.get(ticket_id).unwrap().unwrap(); let lock1 = ticket.read().unwrap(); { let ticket = ticket.read().unwrap(); assert_eq!(ticket_id, ticket.id); assert_eq!(ticket.status, Status::ToDo); assert_eq!(ticket.title, draft.title); assert_eq!(ticket.description, draft.description); } drop(lock1); let ticket = client.get(ticket_id).unwrap().unwrap(); { let mut ticket = ticket.write().unwrap(); ticket.status = Status::InProgress; } } ================================================ FILE: exercises/07_threads/13_without_channels/Cargo.toml ================================================ [package] name = "without_channels" version = "0.1.0" edition = "2021" [dependencies] ticket_fields = { path = "../../../helpers/ticket_fields" } ================================================ FILE: exercises/07_threads/13_without_channels/src/data.rs ================================================ use crate::store::TicketId; use ticket_fields::{TicketDescription, TicketTitle}; #[derive(Clone, Debug, PartialEq)] pub struct Ticket { pub id: TicketId, pub title: TicketTitle, pub description: TicketDescription, pub status: Status, } #[derive(Clone, Debug, PartialEq, Eq)] pub struct TicketDraft { pub title: TicketTitle, pub description: TicketDescription, } #[derive(Clone, Debug, Copy, PartialEq, Eq)] pub enum Status { ToDo, InProgress, Done, } ================================================ FILE: exercises/07_threads/13_without_channels/src/lib.rs ================================================ // TODO: You don't actually have to change anything in the library itself! // We mostly had to **remove** code (the client type, the launch function, the command enum) // that's no longer necessary. // Fix the `todo!()` in the testing code and see how the new design can be used. pub mod data; pub mod store; ================================================ FILE: exercises/07_threads/13_without_channels/src/store.rs ================================================ use std::collections::BTreeMap; use std::sync::{Arc, RwLock}; use crate::data::{Status, Ticket, TicketDraft}; #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] pub struct TicketId(u64); #[derive(Clone)] pub struct TicketStore { tickets: BTreeMap>>, counter: u64, } impl TicketStore { pub fn new() -> Self { Self { tickets: BTreeMap::new(), counter: 0, } } pub fn add_ticket(&mut self, ticket: TicketDraft) -> TicketId { let id = TicketId(self.counter); self.counter += 1; let ticket = Ticket { id, title: ticket.title, description: ticket.description, status: Status::ToDo, }; let ticket = Arc::new(RwLock::new(ticket)); self.tickets.insert(id, ticket); id } pub fn get(&self, id: TicketId) -> Option>> { self.tickets.get(&id).cloned() } } ================================================ FILE: exercises/07_threads/13_without_channels/tests/check.rs ================================================ use std::sync::{Arc, RwLock}; use std::thread::spawn; use ticket_fields::test_helpers::{ticket_description, ticket_title}; use without_channels::data::TicketDraft; use without_channels::store::TicketStore; #[test] fn works() { let store = todo!(); let store1 = store.clone(); let client1 = spawn(move || { let draft = TicketDraft { title: ticket_title(), description: ticket_description(), }; store1.write().unwrap().add_ticket(draft) }); let store2 = store.clone(); let client2 = spawn(move || { let draft = TicketDraft { title: ticket_title(), description: ticket_description(), }; store2.write().unwrap().add_ticket(draft) }); let ticket_id1 = client1.join().unwrap(); let ticket_id2 = client2.join().unwrap(); let reader = store.read().unwrap(); let ticket1 = reader.get(ticket_id1).unwrap(); assert_eq!(ticket_id1, ticket1.read().unwrap().id); let ticket2 = reader.get(ticket_id2).unwrap(); assert_eq!(ticket_id2, ticket2.read().unwrap().id); } ================================================ FILE: exercises/07_threads/14_sync/Cargo.toml ================================================ [package] name = "sync" version = "0.1.0" edition = "2021" ================================================ FILE: exercises/07_threads/14_sync/src/lib.rs ================================================ // Not much to be exercised on `Sync`, just a thing to remember. fn outro() -> &'static str { "I have a good understanding of __!" } #[cfg(test)] mod tests { use crate::outro; #[test] fn test_outro() { assert_eq!(outro(), "I have a good understanding of Send and Sync!"); } } ================================================ FILE: exercises/08_futures/00_intro/Cargo.toml ================================================ [package] name = "intro_08" version = "0.1.0" edition = "2021" ================================================ FILE: exercises/08_futures/00_intro/src/lib.rs ================================================ fn intro() -> &'static str { // TODO: fix me 👇 "I'm ready to _!" } #[cfg(test)] mod tests { use crate::intro; #[test] fn test_intro() { assert_eq!(intro(), "I'm ready to learn about futures!"); } } ================================================ FILE: exercises/08_futures/01_async_fn/Cargo.toml ================================================ [package] name = "async_fn" version = "0.1.0" edition = "2021" [dependencies] anyhow = "1.0.100" tokio = { version = "1", features = ["full"] } ================================================ FILE: exercises/08_futures/01_async_fn/src/lib.rs ================================================ use tokio::net::TcpListener; // TODO: write an echo server that accepts incoming TCP connections and // echoes the received data back to the client. // `echo` should not return when it finishes processing a connection, but should // continue to accept new connections. // // Hint: you should rely on `tokio`'s structs and methods to implement the echo server. // In particular: // - `tokio::net::TcpListener::accept` to process the next incoming connection // - `tokio::net::TcpStream::split` to obtain a reader and a writer from the socket // - `tokio::io::copy` to copy data from the reader to the writer pub async fn echo(listener: TcpListener) -> Result<(), anyhow::Error> { todo!() } #[cfg(test)] mod tests { use super::*; use tokio::io::{AsyncReadExt, AsyncWriteExt}; #[tokio::test] async fn test_echo() { let listener = TcpListener::bind("127.0.0.1:0").await.unwrap(); let addr = listener.local_addr().unwrap(); tokio::spawn(echo(listener)); let requests = vec!["hello", "world", "foo", "bar"]; for request in requests { let mut socket = tokio::net::TcpStream::connect(addr).await.unwrap(); let (mut reader, mut writer) = socket.split(); // Send the request writer.write_all(request.as_bytes()).await.unwrap(); // Close the write side of the socket writer.shutdown().await.unwrap(); // Read the response let mut buf = Vec::with_capacity(request.len()); reader.read_to_end(&mut buf).await.unwrap(); assert_eq!(&buf, request.as_bytes()); } } } ================================================ FILE: exercises/08_futures/02_spawn/Cargo.toml ================================================ [package] name = "spawn" version = "0.1.0" edition = "2021" [dependencies] anyhow = "1.0.100" tokio = { version = "1", features = ["full"] } ================================================ FILE: exercises/08_futures/02_spawn/src/lib.rs ================================================ use tokio::net::TcpListener; // TODO: write an echo server that accepts TCP connections on two listeners, concurrently. // Multiple connections (on the same listeners) should be processed concurrently. // The received data should be echoed back to the client. pub async fn echoes(first: TcpListener, second: TcpListener) -> Result<(), anyhow::Error> { todo!() } #[cfg(test)] mod tests { use super::*; use std::net::SocketAddr; use std::panic; use tokio::io::{AsyncReadExt, AsyncWriteExt}; use tokio::task::JoinSet; async fn bind_random() -> (TcpListener, SocketAddr) { let listener = TcpListener::bind("127.0.0.1:0").await.unwrap(); let addr = listener.local_addr().unwrap(); (listener, addr) } #[tokio::test] async fn test_echo() { let (first_listener, first_addr) = bind_random().await; let (second_listener, second_addr) = bind_random().await; tokio::spawn(echoes(first_listener, second_listener)); let requests = vec!["hello", "world", "foo", "bar"]; let mut join_set = JoinSet::new(); for request in requests.clone() { for addr in [first_addr, second_addr] { join_set.spawn(async move { let mut socket = tokio::net::TcpStream::connect(addr).await.unwrap(); let (mut reader, mut writer) = socket.split(); // Send the request writer.write_all(request.as_bytes()).await.unwrap(); // Close the write side of the socket writer.shutdown().await.unwrap(); // Read the response let mut buf = Vec::with_capacity(request.len()); reader.read_to_end(&mut buf).await.unwrap(); assert_eq!(&buf, request.as_bytes()); }); } } while let Some(outcome) = join_set.join_next().await { if let Err(e) = outcome { if let Ok(reason) = e.try_into_panic() { panic::resume_unwind(reason); } } } } } ================================================ FILE: exercises/08_futures/03_runtime/Cargo.toml ================================================ [package] name = "runtime" version = "0.1.0" edition = "2021" [dependencies] anyhow = "1.0.100" tokio = { version = "1", features = ["full"] } ================================================ FILE: exercises/08_futures/03_runtime/src/lib.rs ================================================ // TODO: Implement the `fixed_reply` function. It should accept two `TcpListener` instances, // accept connections on both of them concurrently, and always reply to clients by sending // the `Display` representation of the `reply` argument as a response. use std::fmt::Display; use tokio::io::AsyncWriteExt; use tokio::net::TcpListener; pub async fn fixed_reply(first: TcpListener, second: TcpListener, reply: T) where // `T` cannot be cloned. How do you share it between the two server tasks? T: Display + Send + Sync + 'static, { todo!() } #[cfg(test)] mod tests { use super::*; use std::net::SocketAddr; use std::panic; use tokio::io::AsyncReadExt; use tokio::task::JoinSet; async fn bind_random() -> (TcpListener, SocketAddr) { let listener = TcpListener::bind("127.0.0.1:0").await.unwrap(); let addr = listener.local_addr().unwrap(); (listener, addr) } #[tokio::test] async fn test_echo() { let (first_listener, first_addr) = bind_random().await; let (second_listener, second_addr) = bind_random().await; let reply = "Yo"; tokio::spawn(fixed_reply(first_listener, second_listener, reply)); let mut join_set = JoinSet::new(); for _ in 0..3 { for addr in [first_addr, second_addr] { join_set.spawn(async move { let mut socket = tokio::net::TcpStream::connect(addr).await.unwrap(); let (mut reader, _) = socket.split(); // Read the response let mut buf = Vec::new(); reader.read_to_end(&mut buf).await.unwrap(); assert_eq!(&buf, reply.as_bytes()); }); } } while let Some(outcome) = join_set.join_next().await { if let Err(e) = outcome { if let Ok(reason) = e.try_into_panic() { panic::resume_unwind(reason); } } } } } ================================================ FILE: exercises/08_futures/04_future/Cargo.toml ================================================ [package] name = "future" version = "0.1.0" edition = "2021" [dependencies] tokio = { version = "1", features = ["full"] } ================================================ FILE: exercises/08_futures/04_future/src/lib.rs ================================================ //! TODO: get the code to compile by **re-ordering** the statements //! in the `example` function. You're not allowed to change the //! `spawner` function nor what each line does in `example`. //! You can wrap existing statements in blocks `{}` if needed. use std::rc::Rc; use tokio::task::yield_now; fn spawner() { tokio::spawn(example()); } async fn example() { let non_send = Rc::new(1); yield_now().await; println!("{}", non_send); } ================================================ FILE: exercises/08_futures/05_blocking/Cargo.toml ================================================ [package] name = "blocking" version = "0.1.0" edition = "2021" [dependencies] anyhow = "1.0.100" tokio = { version = "1", features = ["full"] } ================================================ FILE: exercises/08_futures/05_blocking/src/lib.rs ================================================ // TODO: the `echo` server uses non-async primitives. // When running the tests, you should observe that it hangs, due to a // deadlock between the caller and the server. // Use `spawn_blocking` inside `echo` to resolve the issue. use std::io::{Read, Write}; use tokio::net::TcpListener; pub async fn echo(listener: TcpListener) -> Result<(), anyhow::Error> { loop { let (socket, _) = listener.accept().await?; let mut socket = socket.into_std()?; socket.set_nonblocking(false)?; let mut buffer = Vec::new(); socket.read_to_end(&mut buffer)?; socket.write_all(&buffer)?; } } #[cfg(test)] mod tests { use super::*; use std::net::SocketAddr; use std::panic; use tokio::io::{AsyncReadExt, AsyncWriteExt}; use tokio::task::JoinSet; async fn bind_random() -> (TcpListener, SocketAddr) { let listener = TcpListener::bind("127.0.0.1:0").await.unwrap(); let addr = listener.local_addr().unwrap(); (listener, addr) } #[tokio::test] async fn test_echo() { let (listener, addr) = bind_random().await; tokio::spawn(echo(listener)); let requests = vec![ "hello here we go with a long message", "world", "foo", "bar", ]; let mut join_set = JoinSet::new(); for request in requests { join_set.spawn(async move { let mut socket = tokio::net::TcpStream::connect(addr).await.unwrap(); let (mut reader, mut writer) = socket.split(); // Send the request writer.write_all(request.as_bytes()).await.unwrap(); // Close the write side of the socket writer.shutdown().await.unwrap(); // Read the response let mut buf = Vec::with_capacity(request.len()); reader.read_to_end(&mut buf).await.unwrap(); assert_eq!(&buf, request.as_bytes()); }); } while let Some(outcome) = join_set.join_next().await { if let Err(e) = outcome { if let Ok(reason) = e.try_into_panic() { panic::resume_unwind(reason); } } } } } ================================================ FILE: exercises/08_futures/06_async_aware_primitives/Cargo.toml ================================================ [package] name = "async_locks" version = "0.1.0" edition = "2021" [dependencies] tokio = { version = "1", features = ["full"] } ================================================ FILE: exercises/08_futures/06_async_aware_primitives/src/lib.rs ================================================ /// TODO: the code below will deadlock because it's using std's channels, /// which are not async-aware. /// Rewrite it to use `tokio`'s channels primitive (you'll have to touch /// the testing code too, yes). /// /// Can you understand the sequence of events that can lead to a deadlock? use std::sync::mpsc; pub struct Message { payload: String, response_channel: mpsc::Sender, } /// Replies with `pong` to any message it receives, setting up a new /// channel to continue communicating with the caller. pub async fn pong(mut receiver: mpsc::Receiver) { loop { if let Ok(msg) = receiver.recv() { println!("Pong received: {}", msg.payload); let (sender, new_receiver) = mpsc::channel(); msg.response_channel .send(Message { payload: "pong".into(), response_channel: sender, }) .unwrap(); receiver = new_receiver; } } } #[cfg(test)] mod tests { use crate::{pong, Message}; use std::sync::mpsc; #[tokio::test] async fn ping() { let (sender, receiver) = mpsc::channel(); let (response_sender, response_receiver) = mpsc::channel(); sender .send(Message { payload: "pong".into(), response_channel: response_sender, }) .unwrap(); tokio::spawn(pong(receiver)); let answer = response_receiver.recv().unwrap().payload; assert_eq!(answer, "pong"); } } ================================================ FILE: exercises/08_futures/07_cancellation/Cargo.toml ================================================ [package] name = "cancellation" version = "0.1.0" edition = "2021" [dependencies] tokio = { version = "1", features = ["full"] } ================================================ FILE: exercises/08_futures/07_cancellation/src/lib.rs ================================================ // TODO: fix the `assert_eq` at the end of the tests. // Do you understand why that's the resulting output? use std::time::Duration; use tokio::io::AsyncReadExt; use tokio::net::TcpListener; pub async fn run(listener: TcpListener, n_messages: usize, timeout: Duration) -> Vec { let mut buffer = Vec::new(); for _ in 0..n_messages { let (mut stream, _) = listener.accept().await.unwrap(); let _ = tokio::time::timeout(timeout, async { stream.read_to_end(&mut buffer).await.unwrap(); }) .await; } buffer } #[cfg(test)] mod tests { use super::*; use tokio::io::AsyncWriteExt; #[tokio::test] async fn ping() { let listener = TcpListener::bind("127.0.0.1:0").await.unwrap(); let addr = listener.local_addr().unwrap(); let messages = vec!["hello", "from", "this", "task"]; let timeout = Duration::from_millis(20); let handle = tokio::spawn(run(listener, messages.len(), timeout.clone())); for message in messages { let mut socket = tokio::net::TcpStream::connect(addr).await.unwrap(); let (_, mut writer) = socket.split(); let (beginning, end) = message.split_at(message.len() / 2); // Send first half writer.write_all(beginning.as_bytes()).await.unwrap(); tokio::time::sleep(timeout * 2).await; writer.write_all(end.as_bytes()).await.unwrap(); // Close the write side of the socket let _ = writer.shutdown().await; } let buffered = handle.await.unwrap(); let buffered = std::str::from_utf8(&buffered).unwrap(); assert_eq!(buffered, ""); } } ================================================ FILE: exercises/08_futures/08_outro/Cargo.toml ================================================ [package] name = "outro_08" version = "0.1.0" edition = "2021" [dependencies] tokio = { version = "1", features = ["full"] } ================================================ FILE: exercises/08_futures/08_outro/src/lib.rs ================================================ // This is our last exercise. Let's go down a more unstructured path! // Try writing an **asynchronous REST API** to expose the functionality // of the ticket management system we built throughout the course. // It should expose endpoints to: // - Create a ticket // - Retrieve ticket details // - Patch a ticket // // Use Rust's package registry, crates.io, to find the dependencies you need // (if any) to build this system. ================================================ FILE: helpers/common/Cargo.toml ================================================ [package] name = "common" version = "0.1.0" edition = "2021" [dependencies] ================================================ FILE: helpers/common/src/lib.rs ================================================ pub fn overly_long_description() -> String { "At vero eos et accusamus et iusto odio dignissimos ducimus qui blanditiis praesentium voluptatum deleniti atque corrupti quos dolores et quas molestias excepturi sint occaecati cupiditate non provident, similique sunt in culpa qui officia deserunt mollitia animi, id est laborum et dolorum fuga. Et harum quidem rerum facilis est et expedita distinctio. Nam libero tempore, cum soluta nobis est eligendi optio cumque nihil impedit quo minus id quod maxime placeat facere possimus, omnis voluptas assumenda est, omnis dolor repellendus. Temporibus autem quibusdam et aut officiis debitis aut rerum necessitatibus saepe eveniet ut et voluptates repudiandae sint et molestiae non recusandae. Itaque earum rerum hic tenetur a sapiente delectus, ut aut reiciendis voluptatibus maiores alias consequatur aut perferendis doloribus asperiores repellat.".into() } pub fn overly_long_title() -> String { "A title that's definitely longer than what should be allowed in a development ticket".into() } pub fn valid_title() -> String { "A title".into() } pub fn valid_description() -> String { "A description".into() } ================================================ FILE: helpers/json2redirects.sh ================================================ #!/bin/bash # Ensure the JSON file is provided as an argument if [ "$#" -ne 1 ]; then echo "Usage: $0 " exit 1 fi input_file=$1 # Use jq to parse the JSON and format the output jq -r 'to_entries[] | "/" + .value + " " + .key' "$input_file" ================================================ FILE: helpers/mdbook-exercise-linker/Cargo.toml ================================================ [package] name = "mdbook-exercise-linker" version = "0.1.0" edition = "2021" [dependencies] anyhow = "1.0.100" clap = "4.5.50" mdbook = "0.4.52" semver = "1.0.27" serde_json = "1.0.145" ================================================ FILE: helpers/mdbook-exercise-linker/src/lib.rs ================================================ use anyhow::{Context, Error}; use mdbook::book::Book; use mdbook::preprocess::{Preprocessor, PreprocessorContext}; use mdbook::BookItem; pub struct ExerciseLinker; impl ExerciseLinker { pub fn new() -> ExerciseLinker { ExerciseLinker } } impl Preprocessor for ExerciseLinker { fn name(&self) -> &str { "exercise-linker" } fn run(&self, ctx: &PreprocessorContext, mut book: Book) -> Result { let config = ctx .config .get_preprocessor(self.name()) .context("Failed to get preprocessor configuration")?; let key = String::from("exercise_root_url"); let root_url = config .get(&key) .context("Failed to get `exercise_root_url`")?; let root_url = root_url .as_str() .context("`exercise_root_url` is not a string")? .to_owned(); book.sections .iter_mut() .for_each(|i| process_book_item(i, &ctx.renderer, &root_url)); Ok(book) } fn supports_renderer(&self, _renderer: &str) -> bool { true } } fn process_book_item(item: &mut BookItem, renderer: &str, root_url: &str) { match item { BookItem::Chapter(chapter) => { chapter.sub_items.iter_mut().for_each(|item| { process_book_item(item, renderer, root_url); }); let Some(source_path) = &chapter.source_path else { return; }; let source_path = source_path.display().to_string(); // Ignore non-exercise chapters if !source_path.chars().take(2).all(|c| c.is_digit(10)) { return; } let exercise_path = source_path.strip_suffix(".md").unwrap(); let link_section = format!( "\n## Exercise\n\nThe exercise for this section is located in [`{exercise_path}`]({})\n", format!("{}/{}", root_url, exercise_path) ); chapter.content.push_str(&link_section); if renderer == "pandoc" { chapter.content.push_str("`\\newpage`{=latex}\n"); } } BookItem::Separator => {} BookItem::PartTitle(_) => {} } } ================================================ FILE: helpers/mdbook-exercise-linker/src/main.rs ================================================ use std::io; use std::process; use clap::{Arg, ArgMatches, Command}; use mdbook::errors::Error; use mdbook::preprocess::{CmdPreprocessor, Preprocessor}; use semver::{Version, VersionReq}; use mdbook_exercise_linker::ExerciseLinker; pub fn make_app() -> Command { Command::new("exercise-linker").subcommand( Command::new("supports") .arg(Arg::new("renderer").required(true)) .about("Check whether a renderer is supported by this preprocessor"), ) } fn main() { let matches = make_app().get_matches(); // Users will want to construct their own preprocessor here let preprocessor = ExerciseLinker::new(); if let Some(sub_args) = matches.subcommand_matches("supports") { handle_supports(&preprocessor, sub_args); } else if let Err(e) = handle_preprocessing(&preprocessor) { eprintln!("{}", e); process::exit(1); } } fn handle_preprocessing(pre: &dyn Preprocessor) -> Result<(), Error> { let (ctx, book) = CmdPreprocessor::parse_input(io::stdin())?; let book_version = Version::parse(&ctx.mdbook_version)?; let version_req = VersionReq::parse(mdbook::MDBOOK_VERSION)?; if !version_req.matches(&book_version) { eprintln!( "Warning: The {} plugin was built against version {} of mdbook, \ but we're being called from version {}", pre.name(), mdbook::MDBOOK_VERSION, ctx.mdbook_version ); } let processed_book = pre.run(&ctx, book)?; serde_json::to_writer(io::stdout(), &processed_book)?; Ok(()) } fn handle_supports(pre: &dyn Preprocessor, sub_args: &ArgMatches) -> ! { let renderer = sub_args .get_one::("renderer") .expect("Required argument"); let supported = pre.supports_renderer(renderer); // Signal whether the renderer is supported by exiting with 1 or 0. if supported { process::exit(0); } else { process::exit(1); } } ================================================ FILE: helpers/mdbook-link-shortener/Cargo.toml ================================================ [package] name = "mdbook-link-shortener" version = "0.1.0" edition = "2021" [dependencies] anyhow = "1.0.100" bimap = { version = "0.6.3", features = ["serde"] } clap = { version = "4.5.50", features = ["derive"] } itertools = "0.13.0" mdbook = "0.4.52" pulldown-cmark = "0.11.3" pulldown-cmark-to-cmark = "15" semver = "1.0.27" serde_json = "1.0.145" ================================================ FILE: helpers/mdbook-link-shortener/src/lib.rs ================================================ use anyhow::{Context, Error}; use bimap::BiHashMap; use itertools::Itertools; use mdbook::book::{Book, Chapter}; use mdbook::preprocess::{Preprocessor, PreprocessorContext}; use mdbook::BookItem; use std::collections::{BTreeMap, BTreeSet}; use std::fs::File; use std::path::PathBuf; use std::str::FromStr; pub struct LinkShortener; struct AliasGenerator { cursors: [usize; 3], } impl AliasGenerator { const ALPHABET: &'static [u8] = b"f2z4x6v8bnm3q5w7e9rtyuplkshgjdca"; fn new() -> AliasGenerator { AliasGenerator { cursors: [0, 0, 0] } } /// Generate a 4 alphanumeric long alias, starting from "aaaa" and incrementing by one each time /// until "9999", using only lowercase letters and numbers. /// We skip ambiguous characters like "0", "o", "1", "l". fn next(&mut self) -> String { let mut alias = String::with_capacity(4); for cursor in &mut self.cursors { alias.push(Self::ALPHABET[*cursor] as char); } for cursor in self.cursors.iter_mut().rev() { if *cursor == Self::ALPHABET.len() - 1 { *cursor = 0; } else { *cursor += 1; break; } } alias } /// Generate a unique alias that is not already used by the `link2alias` map. fn next_until_unique(&mut self, link2alias: &BiHashMap) -> String { let mut alias = self.next(); while link2alias.contains_right(&alias) { alias = self.next(); } alias } } impl LinkShortener { pub fn new() -> LinkShortener { LinkShortener } } impl Preprocessor for LinkShortener { fn name(&self) -> &str { "link-shortener" } fn run(&self, ctx: &PreprocessorContext, mut book: Book) -> Result { let config = ctx .config .get_preprocessor(self.name()) .context("Failed to get preprocessor configuration")?; let root_url = { let root_url = config.get("base_url").context("Failed to get `base_url`")?; root_url .as_str() .context("`base_url` is not a string")? .to_owned() }; let mapping = { let mapping = config.get("mapping").context("Failed to get `mapping`")?; let mapping = mapping .as_str() .context("`mapping` is not a string")? .to_owned(); PathBuf::from_str(&mapping).context("Failed to parse `mapping` as a path")? }; let mut link2alias = { match File::open(&mapping) { Ok(file) => { serde_json::from_reader(file).context("Failed to parse existing mapping")? } Err(e) => { if e.kind() == std::io::ErrorKind::NotFound { BiHashMap::new() } else { return Err(e).context("Failed to open existing mapping"); } } } }; let verify = config .get("verify") .context("Failed to get `verify`")? .as_bool() .context("`verify` is not a boolean")?; // Env var overrides config let verify = std::env::var("LINK_SHORTENER_VERIFY") .map(|v| v == "true") .unwrap_or(verify); let mut alias_gen = AliasGenerator::new(); book.sections.iter_mut().for_each(|i| { if let BookItem::Chapter(c) = i { c.content = replace_anchors(c, &root_url, &mut alias_gen, &mut link2alias, verify) .expect("Error converting links for chapter"); for i in c.sub_items.iter_mut() { if let BookItem::Chapter(sub_chapter) = i { sub_chapter.content = replace_anchors( sub_chapter, &root_url, &mut alias_gen, &mut link2alias, verify, ) .expect("Error converting links for subchapter"); } } } }); if !verify { std::fs::create_dir_all(mapping.parent().expect("Mapping file path has no parent"))?; let mut file = File::create(&mapping).context("Failed to upsert mapping file")?; let ordered = link2alias.iter().collect::>(); serde_json::to_writer_pretty(&mut file, &ordered)?; } Ok(book) } fn supports_renderer(&self, _renderer: &str) -> bool { true } } fn replace_anchors( chapter: &mut Chapter, root_url: &str, alias_gen: &mut AliasGenerator, link2alias: &mut BiHashMap, verify: bool, ) -> Result { use pulldown_cmark::{CowStr, Event, LinkType, Options, Parser, Tag}; use pulldown_cmark_to_cmark::cmark; let mut buf = String::with_capacity(chapter.content.len()); let mut unshortened_links = BTreeSet::new(); let events = Parser::new_ext(&chapter.content, Options::all()) .map(|e| { let Event::Start(Tag::Link { link_type, dest_url, title, id, }) = &e else { return e; }; match link_type { LinkType::Autolink | LinkType::Shortcut | LinkType::Inline | LinkType::Reference | LinkType::Collapsed => { if dest_url.starts_with("http") { let alias = if let Some(alias) = link2alias.get_by_left(dest_url.as_ref()) { alias.to_owned() } else { if verify { unshortened_links.insert(dest_url.to_string()); return e; } let alias = alias_gen.next_until_unique(&link2alias); alias }; link2alias.insert(dest_url.to_string(), alias.clone()); Event::Start(Tag::Link { link_type: link_type.to_owned(), dest_url: CowStr::from(format!( "{root_url}/{alias}", root_url = root_url, alias = alias )), title: title.clone(), id: id.clone(), }) } else { e } } LinkType::Email | LinkType::ReferenceUnknown | LinkType::CollapsedUnknown | LinkType::ShortcutUnknown => e, } }) .collect_vec(); if verify && !unshortened_links.is_empty() { let unshortened_links = unshortened_links.iter().join(", "); return Err(anyhow::anyhow!( "The following links are not shortened: {unshortened_links}\nRun again with `LINK_SHORTENER_VERIFY=false` to update the mapping \ with the shortened links." )); } cmark(events.into_iter(), &mut buf) .map(|_| buf) .map_err(|err| anyhow::anyhow!("Markdown serialization failed: {err}")) } ================================================ FILE: helpers/mdbook-link-shortener/src/main.rs ================================================ use clap::Parser; use mdbook::errors::Error; use mdbook::preprocess::{CmdPreprocessor, Preprocessor}; use mdbook_link_shortener::LinkShortener; use semver::{Version, VersionReq}; use std::io; use std::process; #[derive(clap::Parser, Debug)] #[command(version, about)] pub struct Cli { #[command(subcommand)] sub: Option, } #[derive(clap::Parser, Debug)] pub enum SubCommand { #[clap(name = "supports")] Supports(Supports), } #[derive(clap::Parser, Debug)] pub struct Supports { #[arg(long)] renderer: String, } fn main() -> Result<(), anyhow::Error> { let cli = Cli::parse(); let preprocessor = LinkShortener::new(); if let Some(SubCommand::Supports(Supports { renderer })) = cli.sub { let code = if preprocessor.supports_renderer(&renderer) { 0 } else { 1 }; process::exit(code); } handle_preprocessing(&preprocessor)?; Ok(()) } fn handle_preprocessing(pre: &dyn Preprocessor) -> Result<(), Error> { let (ctx, book) = CmdPreprocessor::parse_input(io::stdin())?; let book_version = Version::parse(&ctx.mdbook_version)?; let version_req = VersionReq::parse(mdbook::MDBOOK_VERSION)?; if !version_req.matches(&book_version) { eprintln!( "Warning: The {} plugin was built against version {} of mdbook, \ but we're being called from version {}", pre.name(), mdbook::MDBOOK_VERSION, ctx.mdbook_version ); } let processed_book = pre.run(&ctx, book)?; serde_json::to_writer(io::stdout(), &processed_book)?; Ok(()) } ================================================ FILE: helpers/ticket_fields/Cargo.toml ================================================ [package] name = "ticket_fields" version = "0.1.0" edition = "2021" [dependencies] common = { path = "../common" } thiserror = "1.0.69" ================================================ FILE: helpers/ticket_fields/src/description.rs ================================================ #[derive(Debug, PartialEq, Clone, Eq)] pub struct TicketDescription(String); #[derive(Debug, thiserror::Error)] pub enum TicketDescriptionError { #[error("The description cannot be empty")] Empty, #[error("The description cannot be longer than 500 bytes")] TooLong, } impl TryFrom for TicketDescription { type Error = TicketDescriptionError; fn try_from(value: String) -> Result { validate(&value)?; Ok(Self(value)) } } impl TryFrom<&str> for TicketDescription { type Error = TicketDescriptionError; fn try_from(value: &str) -> Result { validate(value)?; Ok(Self(value.to_string())) } } fn validate(description: &str) -> Result<(), TicketDescriptionError> { if description.is_empty() { Err(TicketDescriptionError::Empty) } else if description.len() > 500 { Err(TicketDescriptionError::TooLong) } else { Ok(()) } } #[cfg(test)] mod tests { use super::*; use common::{overly_long_description, valid_description}; use std::convert::TryFrom; #[test] fn test_try_from_string() { let input = valid_description(); let description = TicketDescription::try_from(input.clone()).unwrap(); assert_eq!(description.0, input); } #[test] fn test_try_from_empty_string() { let err = TicketDescription::try_from("".to_string()).unwrap_err(); assert_eq!(err.to_string(), "The description cannot be empty"); } #[test] fn test_try_from_long_string() { let err = TicketDescription::try_from(overly_long_description()).unwrap_err(); assert_eq!( err.to_string(), "The description cannot be longer than 500 bytes" ); } #[test] fn test_try_from_str() { let description = TicketDescription::try_from("A description").unwrap(); assert_eq!(description.0, "A description"); } } ================================================ FILE: helpers/ticket_fields/src/lib.rs ================================================ mod description; pub mod test_helpers; mod title; pub use description::TicketDescription; pub use title::TicketTitle; ================================================ FILE: helpers/ticket_fields/src/test_helpers.rs ================================================ use crate::{TicketDescription, TicketTitle}; use common::{valid_description, valid_title}; /// A function to generate a valid ticket title, /// for test purposes. pub fn ticket_title() -> TicketTitle { valid_title().try_into().unwrap() } /// A function to generate a valid ticket description, /// for test purposes. pub fn ticket_description() -> TicketDescription { valid_description().try_into().unwrap() } ================================================ FILE: helpers/ticket_fields/src/title.rs ================================================ use std::convert::TryFrom; #[derive(Debug, PartialEq, Clone, Eq)] pub struct TicketTitle(String); #[derive(Debug, thiserror::Error)] pub enum TicketTitleError { #[error("The title cannot be empty")] Empty, #[error("The title cannot be longer than 50 bytes")] TooLong, } impl TryFrom for TicketTitle { type Error = TicketTitleError; fn try_from(value: String) -> Result { validate(&value)?; Ok(Self(value)) } } impl TryFrom<&str> for TicketTitle { type Error = TicketTitleError; fn try_from(value: &str) -> Result { validate(value)?; Ok(Self(value.to_string())) } } fn validate(title: &str) -> Result<(), TicketTitleError> { if title.is_empty() { Err(TicketTitleError::Empty) } else if title.len() > 50 { Err(TicketTitleError::TooLong) } else { Ok(()) } } #[cfg(test)] mod tests { use super::*; use common::{overly_long_title, valid_title}; use std::convert::TryFrom; #[test] fn test_try_from_string() { let input = valid_title(); let title = TicketTitle::try_from(input.clone()).unwrap(); assert_eq!(title.0, input); } #[test] fn test_try_from_empty_string() { let err = TicketTitle::try_from("".to_string()).unwrap_err(); assert_eq!(err.to_string(), "The title cannot be empty"); } #[test] fn test_try_from_long_string() { let err = TicketTitle::try_from(overly_long_title()).unwrap_err(); assert_eq!(err.to_string(), "The title cannot be longer than 50 bytes"); } #[test] fn test_try_from_str() { let title = TicketTitle::try_from("A title").unwrap(); assert_eq!(title.0, "A title"); } } ================================================ FILE: site/_redirects ================================================ /f6c https://code.visualstudio.com /f4q https://crates.io /f2n https://crates.io/crates/cargo-modules /ffr https://doc.rust-lang.org/book/ch03-02-data-types.html#integer-types /f6t https://doc.rust-lang.org/book/title-page.html /f4m https://doc.rust-lang.org/cargo/reference/cargo-targets.html#cargo-targets /ffc https://doc.rust-lang.org/cargo/reference/profiles.html /f45 https://doc.rust-lang.org/cargo/reference/specifying-dependencies.html /f6u https://doc.rust-lang.org/nomicon/ /f2z https://doc.rust-lang.org/reference/expressions/operator-expr.html#numeric-cast /fzf https://doc.rust-lang.org/reference/items/implementations.html#trait-implementation-coherence /f4c https://doc.rust-lang.org/reference/lifetime-elision.html /fxy https://doc.rust-lang.org/std/cell/struct.UnsafeCell.html /fzm https://doc.rust-lang.org/std/cmp/index.html /fzz https://doc.rust-lang.org/std/cmp/trait.PartialEq.html /fzb https://doc.rust-lang.org/std/cmp/trait.PartialOrd.html /fzp https://doc.rust-lang.org/std/convert/trait.From.html#implementors /fzl https://doc.rust-lang.org/std/convert/trait.Into.html#implementors /f4s https://doc.rust-lang.org/std/iter/trait.FusedIterator.html /fxf https://doc.rust-lang.org/std/iter/trait.Iterator.html /ffj https://doc.rust-lang.org/std/keyword.for.html /ffh https://doc.rust-lang.org/std/keyword.while.html /ffl https://doc.rust-lang.org/std/macro.panic.html /f27 https://doc.rust-lang.org/std/mem/fn.size_of.html /fzn https://doc.rust-lang.org/std/ops/index.html /fz4 https://doc.rust-lang.org/std/ops/trait.Add.html /fzt https://doc.rust-lang.org/std/ops/trait.Deref.html#deref-coercion /fzv https://doc.rust-lang.org/std/ops/trait.Div.html /fz6 https://doc.rust-lang.org/std/ops/trait.Mul.html /fz8 https://doc.rust-lang.org/std/ops/trait.Rem.html /fzx https://doc.rust-lang.org/std/ops/trait.Sub.html /f2c https://doc.rust-lang.org/std/prelude/index.html /ffe https://doc.rust-lang.org/std/primitive.i32.html#associatedconstant.MAX /ff7 https://doc.rust-lang.org/std/primitive.i32.html#associatedconstant.MIN /ffw https://doc.rust-lang.org/std/primitive.u32.html#associatedconstant.MAX /f4d https://doc.rust-lang.org/std/slice/struct.Iter.html /f26 https://doc.rust-lang.org/std/string/struct.String.html /fxh https://doc.rust-lang.org/std/sync/atomic/index.html /f4j https://doc.rust-lang.org/std/vec/struct.Vec.html#method.iter /f2y https://docs.rs/dhat/latest/dhat/ /fx2 https://docs.rs/itertools/ /f4n https://docs.rs/thiserror/latest/thiserror/ /f65 https://docs.rs/tokio-stream/latest/tokio_stream/ /f6m https://docs.rs/tokio-stream/latest/tokio_stream/trait.StreamExt.html#method.merge /f63 https://docs.rs/tokio-util/latest/tokio_util/sync/struct.CancellationToken.html /f6z https://docs.rs/tokio/latest/tokio/task/struct.JoinError.html /f6k https://docs.rust-embedded.org/book/ /f2h https://en.wikipedia.org/wiki/Dangling_pointer /fx7 https://en.wikipedia.org/wiki/Data_segment /f2r https://en.wikipedia.org/wiki/Memory_address /f2e https://en.wikipedia.org/wiki/Stack_overflow /ff9 https://en.wikipedia.org/wiki/Two%27s_complement /f2v https://en.wikipedia.org/wiki/UTF-8 /f6r https://exercism.io /ffb https://github.com/LukeMathWalker/cargo-chef /ffm https://github.com/LukeMathWalker/wiremock-rs /fzq https://github.com/dtolnay/cargo-expand /fzw https://github.com/dtolnay/proc-macro-workshop /ff6 https://github.com/mainmatter/100-exercises-to-learn-rust /ff3 https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/01_intro/00_welcome /ffq https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/01_intro/01_syntax /ff5 https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/02_basic_calculator/00_intro /fft https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/02_basic_calculator/01_integers /ffy https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/02_basic_calculator/02_variables /ffu https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/02_basic_calculator/03_if_else /ffk https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/02_basic_calculator/04_panics /ffs https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/02_basic_calculator/05_factorial /ffg https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/02_basic_calculator/06_while /ffd https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/02_basic_calculator/07_for /f2f https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/02_basic_calculator/08_overflow /f22 https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/02_basic_calculator/09_saturating /f24 https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/02_basic_calculator/10_as_casting /f2x https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/03_ticket_v1/00_intro /f28 https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/03_ticket_v1/01_struct /f2b https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/03_ticket_v1/02_validation /f2m https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/03_ticket_v1/03_modules /f23 https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/03_ticket_v1/04_visibility /f2q https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/03_ticket_v1/05_encapsulation /f25 https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/03_ticket_v1/06_ownership /f2w https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/03_ticket_v1/07_setters /f29 https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/03_ticket_v1/08_stack /f2p https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/03_ticket_v1/09_heap /f2l https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/03_ticket_v1/10_references_in_memory /f2g https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/03_ticket_v1/11_destructor /f2j https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/03_ticket_v1/12_outro /f2d https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/04_traits/00_intro /f2a https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/04_traits/01_trait /fz2 https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/04_traits/02_orphan_rule /fz3 https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/04_traits/03_operator_overloading /fz7 https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/04_traits/04_derive /fz9 https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/04_traits/05_trait_bounds /fzr https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/04_traits/06_str_slice /fzy https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/04_traits/07_deref /fzu https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/04_traits/08_sized /fzk https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/04_traits/09_from /fzs https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/04_traits/10_assoc_vs_generic /fzh https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/04_traits/11_clone /fzg https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/04_traits/12_copy /fzj https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/04_traits/13_drop /fzc https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/04_traits/14_outro /fza https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/05_ticket_v2/00_intro /f4f https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/05_ticket_v2/01_enum /f42 https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/05_ticket_v2/02_match /f4z https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/05_ticket_v2/03_variants_with_data /f44 https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/05_ticket_v2/04_if_let /f4x https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/05_ticket_v2/05_nullability /f46 https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/05_ticket_v2/06_fallibility /f4v https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/05_ticket_v2/07_unwrap /f48 https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/05_ticket_v2/08_error_enums /f4b https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/05_ticket_v2/09_error_trait /f43 https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/05_ticket_v2/10_packages /f4w https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/05_ticket_v2/11_dependencies /f47 https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/05_ticket_v2/12_thiserror /f4e https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/05_ticket_v2/13_try_from /f49 https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/05_ticket_v2/14_source /f4y https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/05_ticket_v2/15_outro /f4u https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/06_ticket_management/00_intro /f4p https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/06_ticket_management/01_arrays /f4l https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/06_ticket_management/02_vec /f4k https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/06_ticket_management/03_resizing /f4h https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/06_ticket_management/04_iterators /f4g https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/06_ticket_management/05_iter /f4a https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/06_ticket_management/06_lifetimes /fxz https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/06_ticket_management/07_combinators /fx4 https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/06_ticket_management/08_impl_trait /fxx https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/06_ticket_management/09_impl_trait_2 /fx6 https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/06_ticket_management/10_slices /fxv https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/06_ticket_management/11_mutable_slices /fx8 https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/06_ticket_management/12_two_states /fxb https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/06_ticket_management/13_index /fxn https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/06_ticket_management/14_index_mut /fxm https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/06_ticket_management/15_hashmap /fx3 https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/06_ticket_management/16_btreemap /fxq https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/07_threads/00_intro /fxw https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/07_threads/01_threads /fxe https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/07_threads/02_static /fx9 https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/07_threads/03_leak /fxr https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/07_threads/04_scoped_threads /fxt https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/07_threads/05_channels /fxu https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/07_threads/06_interior_mutability /fxp https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/07_threads/07_ack /fxl https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/07_threads/08_client /fxk https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/07_threads/09_bounded /fxs https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/07_threads/10_patch /fxj https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/07_threads/11_locks /fxd https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/07_threads/12_rw_lock /fxc https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/07_threads/13_without_channels /fxa https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/07_threads/14_sync /f6f https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/08_futures/00_intro /f62 https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/08_futures/01_async_fn /f64 https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/08_futures/02_spawn /f6x https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/08_futures/03_runtime /f66 https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/08_futures/04_future /f68 https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/08_futures/05_blocking /f6b https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/08_futures/06_async_aware_primitives /f6q https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/08_futures/07_cancellation /f6e https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/08_futures/08_outro /ffz https://github.com/mainmatter/100-exercises-to-learn-rust/tree/solutions /fzd https://github.com/mainmatter/rust-advanced-testing-workshop /f69 https://github.com/rust-lang/rustlings /ffa https://huonw.github.io/blog/2016/04/myths-and-legends-about-integer-overflow-in-rust/ /f4r https://lexi-lambda.github.io/blog/2019/11/05/parse-don-t-validate/ /ff2 https://mainmatter.com/contact/ /fff https://mainmatter.com/rust-consulting/ /fv2 https://mainmatter.github.io/rust-workshop-runner/ /fxg https://marabos.nl/atomics/ /f6a https://marketplace.visualstudio.com/items?itemName=matklad.rust-analyzer /f6p https://nostarch.com/rust-rustaceans /f2k https://owasp.org/www-community/vulnerabilities/Doubly_freeing_memory /f2s https://owasp.org/www-community/vulnerabilities/Using_freed_memory /ffn https://pavex.dev /ffp https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=36e5ddbe3b3f741dfa9f74c956622bac /fx5 https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=afedf7062298ca8f5a248bc551062eaa /ffx https://rust-exercises.com/100-exercises-to-learn-rust.pdf /ff4 https://rust-exercises.com/100-exercises/ /f6s https://rust-exercises.com/advanced-testing/ /f6h https://rust-exercises.com/telemetry/ /fze https://rust-lang.github.io/api-guidelines/naming.html#casing-conforms-to-rfc-430-c-case /f6w https://rust-lang.github.io/wg-async/vision/submitted_stories/status_quo/barbara_battles_buffered_streams.html /f6v https://ryhl.io/blog/async-what-is-blocking/ /fvf https://ti.to/mainmatter/rust-from-scratch-jan-2025 /f6n https://tokio.rs/tokio/tutorial/select /f2t https://valgrind.org/docs/manual/dh-manual.html /fz5 https://veykril.github.io/tlborm/ /f2u https://web.archive.org/web/20240517051950/https://blog.acolyer.org/2019/05/28/cheri-abi/ /f67 https://without.boats/blog/the-scoped-task-trilemma/ /f6g https://www.amazon.com/dp/B0DJ14KQQG/ /f6d https://www.jetbrains.com/rust/ /ffv https://www.lpalmieri.com/ /f4t https://www.lpalmieri.com/posts/2020-12-11-zero-to-production-6-domain-modelling/ /f6y https://www.oreilly.com/library/view/programming-rust-2nd/9781492052586/ /f6j https://www.rust-lang.org/tools/install /f6l https://www.youtube.com/playlist?list=PLqbS7AVVErFirH9armw8yXlE6dacF-A6z /ff8 https://zero2prod.com