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