Compare commits
72 Commits
my_solutio
...
9e63a5cdf6
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9e63a5cdf6 | ||
|
|
f4a7472f30 | ||
|
|
04b89ff26e | ||
|
|
26e78072cd | ||
|
|
c540242c15 | ||
|
|
12fb62d8be | ||
|
|
0bf398a351 | ||
|
|
f14a047e8b | ||
|
|
5e23bee61e | ||
|
|
8488317564 | ||
|
|
bd17a59e8e | ||
|
|
8c3e40033b | ||
|
|
a392371ef0 | ||
|
|
f4cb4b55d0 | ||
|
|
45c32d8e41 | ||
|
|
4ca49fa5fd | ||
|
|
c437f75fcf | ||
|
|
f839fc00f9 | ||
|
|
954dd0ff14 | ||
|
|
33bcaf0a7a | ||
|
|
0aecb5380e | ||
|
|
276bdbbcdc | ||
|
|
a899ecec96 | ||
|
|
a0537f8c51 | ||
|
|
73649001ba | ||
|
|
99ee4b27c5 | ||
|
|
af6c605fe6 | ||
|
|
f613c9a58b | ||
|
|
e9c4f37130 | ||
|
|
e768d9dbd8 | ||
|
|
ea71ac2c0a | ||
|
|
0ca42ba058 | ||
|
|
681903a853 | ||
|
|
6bdcf19f5f | ||
|
|
297d78d750 | ||
|
|
cab4de6433 | ||
|
|
52c21a241d | ||
|
|
982a8f0789 | ||
|
|
1b050774e7 | ||
|
|
d269320825 | ||
|
|
8730a8458a | ||
|
|
97d13e5c85 | ||
|
|
d5918375f3 | ||
|
|
fb8f83a518 | ||
|
|
f9a1d427b2 | ||
|
|
05e3efd298 | ||
|
|
c9ed60daf5 | ||
|
|
0910f3909f | ||
|
|
2765fdaa0e | ||
|
|
96f06708b0 | ||
|
|
e732ea82e4 | ||
|
|
5ef0a6aa12 | ||
|
|
f882f0416d | ||
|
|
6029a8fc17 | ||
|
|
056505d89f | ||
|
|
b039a6c5c2 | ||
|
|
3a9c9ea520 | ||
|
|
3f4d31148f | ||
|
|
2f067058ce | ||
|
|
be5c0e8bae | ||
|
|
a6056381bd | ||
|
|
59833f2a55 | ||
|
|
9a2086081c | ||
|
|
f272843c61 | ||
|
|
fccad08921 | ||
|
|
de45f8adf2 | ||
|
|
5660a2f7a8 | ||
|
|
491319a6d5 | ||
|
|
83cf1cad62 | ||
|
|
d8d7e73f1c | ||
|
|
468de3c0ac | ||
|
|
c86360f3c4 |
126
.github/workflows/ci.yml
vendored
126
.github/workflows/ci.yml
vendored
@@ -7,6 +7,10 @@ on:
|
|||||||
pull_request:
|
pull_request:
|
||||||
branches:
|
branches:
|
||||||
- main
|
- main
|
||||||
|
workflow_dispatch:
|
||||||
|
schedule:
|
||||||
|
# First day of a month
|
||||||
|
- cron: '0 0 1 * *'
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
@@ -15,16 +19,93 @@ jobs:
|
|||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
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
|
- uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||||
- name: Install plugin
|
- name: Install exercise plugin
|
||||||
run: cargo install --path helpers/mdbook-exercise-linker
|
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: teatimeguest/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
|
- uses: taiki-e/install-action@v2
|
||||||
with:
|
with:
|
||||||
tool: mdbook
|
tool: mdbook
|
||||||
- name: Build book
|
- name: Build book
|
||||||
|
env:
|
||||||
|
LINK_SHORTENER_VERIFY: "true"
|
||||||
run: |
|
run: |
|
||||||
cd book
|
cd book
|
||||||
mdbook build
|
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|<code>\\newpage</code>{=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
|
- name: Link Checker
|
||||||
uses: lycheeverse/lychee-action@v1
|
uses: lycheeverse/lychee-action@v1
|
||||||
with:
|
with:
|
||||||
@@ -33,24 +114,37 @@ jobs:
|
|||||||
--exclude-loopback
|
--exclude-loopback
|
||||||
--require-https
|
--require-https
|
||||||
--no-progress
|
--no-progress
|
||||||
book/book
|
book/book/html/
|
||||||
# Upload the book as an artifact
|
# Upload the HTML book as an artifact
|
||||||
- uses: actions/upload-artifact@v4
|
- uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: book
|
name: book
|
||||||
path: book/book
|
# When you support multiple formats, the output directory changes
|
||||||
# Commit and push all changed files.
|
# to include the format in its path.
|
||||||
# Must only affect files that are listed in "paths-ignore".
|
path: book/book/html
|
||||||
- name: Git commit build artifacts
|
- uses: actions/upload-artifact@v4
|
||||||
# Only run on main branch push (e.g. pull request merge).
|
with:
|
||||||
if: github.event_name == 'push'
|
name: online-pdf
|
||||||
run: |
|
path: book/book/pandoc/pdf/100-exercises-to-learn-rust.pdf
|
||||||
git checkout -b deploy
|
- uses: actions/upload-artifact@v4
|
||||||
git config --global user.name "Deployer"
|
with:
|
||||||
git config --global user.email "username@users.noreply.github.com"
|
name: paperback
|
||||||
git add --force book/book
|
path: book/book/pandoc/paperback/100-exercises-to-learn-rust.pdf
|
||||||
git commit -m "Render book"
|
- uses: actions/upload-artifact@v4
|
||||||
git push --set-upstream --force-with-lease origin deploy
|
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
|
||||||
|
|
||||||
formatter:
|
formatter:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|||||||
463
Cargo.lock
generated
463
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
13
Cargo.toml
13
Cargo.toml
@@ -1,3 +1,14 @@
|
|||||||
[workspace]
|
[workspace]
|
||||||
members = ["exercises/*/*", "helpers/common", "helpers/mdbook-exercise-linker", "helpers/ticket_fields"]
|
members = [
|
||||||
|
"exercises/*/*",
|
||||||
|
"helpers/common",
|
||||||
|
"helpers/mdbook-exercise-linker",
|
||||||
|
"helpers/mdbook-link-shortener",
|
||||||
|
"helpers/ticket_fields",
|
||||||
|
]
|
||||||
resolver = "2"
|
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
|
||||||
|
|||||||
BIN
book/assets/back.pdf
Normal file
BIN
book/assets/back.pdf
Normal file
Binary file not shown.
BIN
book/assets/cover.pdf
Normal file
BIN
book/assets/cover.pdf
Normal file
Binary file not shown.
123
book/book.toml
123
book/book.toml
@@ -1,12 +1,129 @@
|
|||||||
[book]
|
[book]
|
||||||
authors = ["Luca Palmieri (Mainmatter)"]
|
authors = ["Luca Palmieri"]
|
||||||
language = "en"
|
language = "en"
|
||||||
multilingual = false
|
multilingual = false
|
||||||
src = "src"
|
src = "src"
|
||||||
title = "100 Exercises To Learn Rust"
|
title = "100 Exercises To Learn Rust"
|
||||||
|
|
||||||
|
[preprocessor.exercise-linker]
|
||||||
|
exercise_root_url = "https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises"
|
||||||
|
|
||||||
|
[preprocessor.link-shortener]
|
||||||
|
base_url = "https://ruex.io"
|
||||||
|
renderers = ["pandoc"]
|
||||||
|
mapping = "link2alias.json"
|
||||||
|
verify = false
|
||||||
|
after = ["exercise-linker"]
|
||||||
|
|
||||||
[output.html]
|
[output.html]
|
||||||
git-repository-url = "https://github.com/mainmatter/100-exercises-to-learn-rust"
|
git-repository-url = "https://github.com/mainmatter/100-exercises-to-learn-rust"
|
||||||
|
|
||||||
[preprocessor.exercise-linker]
|
[output.pandoc]
|
||||||
exercise_root_url = "https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises"
|
optional = true
|
||||||
|
hosted-html = "https://rust-exercises.com/100-exercises/"
|
||||||
|
|
||||||
|
[output.pandoc.code]
|
||||||
|
show-hidden-lines = true
|
||||||
|
|
||||||
|
[output.pandoc.profile.pdf] # options to pass to Pandoc (see https://pandoc.org/MANUAL.html)
|
||||||
|
output-file = "100-exercises-to-learn-rust.pdf"
|
||||||
|
to = "latex"
|
||||||
|
highlight-style = "./custom.theme"
|
||||||
|
# We use `lualatext` because, right now, it's the only engine
|
||||||
|
# that supports fallback fonts, which we need for emojis.
|
||||||
|
pdf-engine = "lualatex"
|
||||||
|
metadata-file = "metadata.yml"
|
||||||
|
|
||||||
|
[output.pandoc.profile.pdf.variables]
|
||||||
|
mainfont = "CoreSansA45.ttf"
|
||||||
|
mainfontoptions = [
|
||||||
|
"BoldFont=CoreSansA65.ttf",
|
||||||
|
"ItalicFont=CoreSansA45It.ttf",
|
||||||
|
"BoldItalicFont=CoreSansA65It.ttf",
|
||||||
|
]
|
||||||
|
sansfont = "CoreSansA45.ttf"
|
||||||
|
sansfontoptions = [
|
||||||
|
"BoldFont=CoreSansA65.ttf",
|
||||||
|
"ItalicFont=CoreSansA45It.ttf",
|
||||||
|
"BoldItalicFont=CoreSansA65It.ttf",
|
||||||
|
]
|
||||||
|
# You can get these fonts here: https://fonts.google.com/selection?query=noto+color+
|
||||||
|
monofont = "Noto Sans Mono"
|
||||||
|
mainfontfallback = ["Open Sans"]
|
||||||
|
sansfontfallback = ["Open Sans"]
|
||||||
|
monofontfallback = [
|
||||||
|
"Noto Color Emoji:mode=harf",
|
||||||
|
]
|
||||||
|
linkcolor = "Links"
|
||||||
|
urlcolor = "Links"
|
||||||
|
urlstyle = "rm"
|
||||||
|
documentclass = "book"
|
||||||
|
fontsize = "10pt"
|
||||||
|
geometry = "papersize={8in,10in},top=2cm,bottom=2cm,left=2.4cm,right=2.4cm"
|
||||||
|
header-includes = [
|
||||||
|
"\\definecolor{Links}{HTML}{6200EE}",
|
||||||
|
# Reduce font size of code blocks
|
||||||
|
"\\DefineVerbatimEnvironment{Highlighting}{Verbatim}{commandchars=\\\\\\{\\},fontsize=\\small}",
|
||||||
|
]
|
||||||
|
|
||||||
|
[output.pandoc.profile.paperback]
|
||||||
|
output-file = "100-exercises-to-learn-rust.pdf"
|
||||||
|
to = "latex"
|
||||||
|
highlight-style = "monochrome"
|
||||||
|
metadata-file = "metadata.yml"
|
||||||
|
# We use `lualatext` because, right now, it's the only engine
|
||||||
|
# that supports fallback fonts, which we need for emojis.
|
||||||
|
pdf-engine = "lualatex"
|
||||||
|
|
||||||
|
[output.pandoc.profile.paperback.variables]
|
||||||
|
mainfont = "CoreSansA45.ttf"
|
||||||
|
mainfontoptions = [
|
||||||
|
"BoldFont=CoreSansA65.ttf",
|
||||||
|
"ItalicFont=CoreSansA45It.ttf",
|
||||||
|
"BoldItalicFont=CoreSansA65It.ttf",
|
||||||
|
]
|
||||||
|
sansfont = "CoreSansA45.ttf"
|
||||||
|
sansfontoptions = [
|
||||||
|
"BoldFont=CoreSansA65.ttf",
|
||||||
|
"ItalicFont=CoreSansA45It.ttf",
|
||||||
|
"BoldItalicFont=CoreSansA65It.ttf",
|
||||||
|
]
|
||||||
|
# You can get these fonts here: https://fonts.google.com/selection?query=noto+color+
|
||||||
|
monofont = "Noto Sans Mono"
|
||||||
|
mainfontfallback = ["Open Sans"]
|
||||||
|
sansfontfallback = ["Open Sans"]
|
||||||
|
monofontfallback = [
|
||||||
|
"Noto Color Emoji:mode=harf",
|
||||||
|
]
|
||||||
|
linkcolor = "Links"
|
||||||
|
urlcolor = "Links"
|
||||||
|
urlstyle = "rm"
|
||||||
|
documentclass = "book"
|
||||||
|
fontsize = "10pt"
|
||||||
|
geometry = "papersize={8in,10in},top=2cm,bottom=2cm,left=2.8cm,right=2.5cm"
|
||||||
|
header-includes = [
|
||||||
|
"\\definecolor{Links}{HTML}{6200EE}",
|
||||||
|
# Reduce font size of code blocks
|
||||||
|
"\\DefineVerbatimEnvironment{Highlighting}{Verbatim}{commandchars=\\\\\\{\\},fontsize=\\small}",
|
||||||
|
]
|
||||||
|
links-as-notes = true
|
||||||
|
|
||||||
|
# We go through HTML, rather than directly to ePUB, since routing
|
||||||
|
# Pandoc's HTML through Calibre's ePUB converter gives us better results.
|
||||||
|
[output.pandoc.profile.html]
|
||||||
|
output-file = "100-exercises-to-learn-rust.html"
|
||||||
|
to = "html"
|
||||||
|
highlight-style = "monochrome"
|
||||||
|
embed-resources = true
|
||||||
|
standalone = true
|
||||||
|
metadata-file = "metadata.yml"
|
||||||
|
|
||||||
|
[output.pandoc.profile.html.variables]
|
||||||
|
# You can get these fonts here: https://fonts.google.com/selection?query=noto+color+
|
||||||
|
monofont = "Noto Sans Mono"
|
||||||
|
mainfontfallback = ["Open Sans"]
|
||||||
|
sansfontfallback = ["Open Sans"]
|
||||||
|
monofontfallback = [
|
||||||
|
"Noto Color Emoji:mode=harf",
|
||||||
|
]
|
||||||
|
urlstyle = "rm"
|
||||||
|
|||||||
211
book/custom.theme
Normal file
211
book/custom.theme
Normal file
@@ -0,0 +1,211 @@
|
|||||||
|
{
|
||||||
|
"text-color": "#c9d1d9",
|
||||||
|
"background-color": "#343942",
|
||||||
|
"line-number-color": null,
|
||||||
|
"line-number-background-color": null,
|
||||||
|
"text-styles": {
|
||||||
|
"Alert": {
|
||||||
|
"text-color": "#ff0000",
|
||||||
|
"background-color": null,
|
||||||
|
"bold": true,
|
||||||
|
"italic": false,
|
||||||
|
"underline": false
|
||||||
|
},
|
||||||
|
"Annotation": {
|
||||||
|
"text-color": "#8b949e",
|
||||||
|
"background-color": null,
|
||||||
|
"bold": true,
|
||||||
|
"italic": true,
|
||||||
|
"underline": false
|
||||||
|
},
|
||||||
|
"Attribute": {
|
||||||
|
"text-color": "#03DAC5",
|
||||||
|
"background-color": null,
|
||||||
|
"bold": false,
|
||||||
|
"italic": false,
|
||||||
|
"underline": false
|
||||||
|
},
|
||||||
|
"BaseN": {
|
||||||
|
"text-color": "#a5d6ff",
|
||||||
|
"background-color": null,
|
||||||
|
"bold": false,
|
||||||
|
"italic": false,
|
||||||
|
"underline": false
|
||||||
|
},
|
||||||
|
"BuiltIn": {
|
||||||
|
"text-color": "#03DAC5",
|
||||||
|
"background-color": null,
|
||||||
|
"bold": false,
|
||||||
|
"italic": false,
|
||||||
|
"underline": false
|
||||||
|
},
|
||||||
|
"Char": {
|
||||||
|
"text-color": "#a5d6ff",
|
||||||
|
"background-color": null,
|
||||||
|
"bold": false,
|
||||||
|
"italic": false,
|
||||||
|
"underline": false
|
||||||
|
},
|
||||||
|
"Comment": {
|
||||||
|
"text-color": "#8b949e",
|
||||||
|
"background-color": null,
|
||||||
|
"bold": false,
|
||||||
|
"italic": true,
|
||||||
|
"underline": false
|
||||||
|
},
|
||||||
|
"CommentVar": {
|
||||||
|
"text-color": "#8b949e",
|
||||||
|
"background-color": null,
|
||||||
|
"bold": true,
|
||||||
|
"italic": true,
|
||||||
|
"underline": false
|
||||||
|
},
|
||||||
|
"Constant": {
|
||||||
|
"text-color": "#79c0ff",
|
||||||
|
"background-color": null,
|
||||||
|
"bold": false,
|
||||||
|
"italic": false,
|
||||||
|
"underline": false
|
||||||
|
},
|
||||||
|
"ControlFlow": {
|
||||||
|
"text-color": "#03DAC5",
|
||||||
|
"background-color": null,
|
||||||
|
"bold": true,
|
||||||
|
"italic": false,
|
||||||
|
"underline": false
|
||||||
|
},
|
||||||
|
"DataType": {
|
||||||
|
"text-color": "#8b949e",
|
||||||
|
"background-color": null,
|
||||||
|
"bold": false,
|
||||||
|
"italic": false,
|
||||||
|
"underline": false
|
||||||
|
},
|
||||||
|
"DecVal": {
|
||||||
|
"text-color": "#79c0ff",
|
||||||
|
"background-color": null,
|
||||||
|
"bold": false,
|
||||||
|
"italic": false,
|
||||||
|
"underline": false
|
||||||
|
},
|
||||||
|
"Documentation": {
|
||||||
|
"text-color": "#8b949e",
|
||||||
|
"background-color": null,
|
||||||
|
"bold": false,
|
||||||
|
"italic": true,
|
||||||
|
"underline": false
|
||||||
|
},
|
||||||
|
"Error": {
|
||||||
|
"text-color": "#ff0000",
|
||||||
|
"background-color": null,
|
||||||
|
"bold": true,
|
||||||
|
"italic": false,
|
||||||
|
"underline": false
|
||||||
|
},
|
||||||
|
"Extension": {
|
||||||
|
"text-color": null,
|
||||||
|
"background-color": null,
|
||||||
|
"bold": false,
|
||||||
|
"italic": false,
|
||||||
|
"underline": false
|
||||||
|
},
|
||||||
|
"Float": {
|
||||||
|
"text-color": "#79c0ff",
|
||||||
|
"background-color": null,
|
||||||
|
"bold": false,
|
||||||
|
"italic": false,
|
||||||
|
"underline": false
|
||||||
|
},
|
||||||
|
"Function": {
|
||||||
|
"text-color": "#d2a8ff",
|
||||||
|
"background-color": null,
|
||||||
|
"bold": false,
|
||||||
|
"italic": false,
|
||||||
|
"underline": false
|
||||||
|
},
|
||||||
|
"Import": {
|
||||||
|
"text-color": "#8b949e",
|
||||||
|
"background-color": null,
|
||||||
|
"bold": true,
|
||||||
|
"italic": false,
|
||||||
|
"underline": false
|
||||||
|
},
|
||||||
|
"Information": {
|
||||||
|
"text-color": "#8b949e",
|
||||||
|
"background-color": null,
|
||||||
|
"bold": true,
|
||||||
|
"italic": true,
|
||||||
|
"underline": false
|
||||||
|
},
|
||||||
|
"Keyword": {
|
||||||
|
"text-color": "#03DAC5",
|
||||||
|
"background-color": null,
|
||||||
|
"bold": true,
|
||||||
|
"italic": false,
|
||||||
|
"underline": false
|
||||||
|
},
|
||||||
|
"Operator": {
|
||||||
|
"text-color": "#03DAC5",
|
||||||
|
"background-color": null,
|
||||||
|
"bold": false,
|
||||||
|
"italic": false,
|
||||||
|
"underline": false
|
||||||
|
},
|
||||||
|
"Other": {
|
||||||
|
"text-color": "#007020",
|
||||||
|
"background-color": null,
|
||||||
|
"bold": false,
|
||||||
|
"italic": false,
|
||||||
|
"underline": false
|
||||||
|
},
|
||||||
|
"Preprocessor": {
|
||||||
|
"text-color": "#03DAC5",
|
||||||
|
"background-color": null,
|
||||||
|
"bold": false,
|
||||||
|
"italic": false,
|
||||||
|
"underline": false
|
||||||
|
},
|
||||||
|
"SpecialChar": {
|
||||||
|
"text-color": "#03DAC5",
|
||||||
|
"background-color": null,
|
||||||
|
"bold": false,
|
||||||
|
"italic": false,
|
||||||
|
"underline": false
|
||||||
|
},
|
||||||
|
"SpecialString": {
|
||||||
|
"text-color": "#03DAC5",
|
||||||
|
"background-color": null,
|
||||||
|
"bold": false,
|
||||||
|
"italic": false,
|
||||||
|
"underline": false
|
||||||
|
},
|
||||||
|
"String": {
|
||||||
|
"text-color": "#a5d6ff",
|
||||||
|
"background-color": null,
|
||||||
|
"bold": false,
|
||||||
|
"italic": false,
|
||||||
|
"underline": false
|
||||||
|
},
|
||||||
|
"Variable": {
|
||||||
|
"text-color": "#a8daff",
|
||||||
|
"background-color": null,
|
||||||
|
"bold": false,
|
||||||
|
"italic": false,
|
||||||
|
"underline": false
|
||||||
|
},
|
||||||
|
"VerbatimString": {
|
||||||
|
"text-color": "#a5d6ff",
|
||||||
|
"background-color": null,
|
||||||
|
"bold": false,
|
||||||
|
"italic": false,
|
||||||
|
"underline": false
|
||||||
|
},
|
||||||
|
"Warning": {
|
||||||
|
"text-color": "#60a0b0",
|
||||||
|
"background-color": null,
|
||||||
|
"bold": true,
|
||||||
|
"italic": true,
|
||||||
|
"underline": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
190
book/link2alias.json
Normal file
190
book/link2alias.json
Normal file
@@ -0,0 +1,190 @@
|
|||||||
|
{
|
||||||
|
"https://blog.acolyer.org/2019/05/28/cheri-abi/": "f2u",
|
||||||
|
"https://crates.io": "f4q",
|
||||||
|
"https://crates.io/crates/cargo-modules": "f2n",
|
||||||
|
"https://doc.rust-lang.org/book/ch03-02-data-types.html#integer-types": "ffr",
|
||||||
|
"https://doc.rust-lang.org/book/title-page.html": "f6t",
|
||||||
|
"https://doc.rust-lang.org/cargo/reference/cargo-targets.html#cargo-targets": "f4m",
|
||||||
|
"https://doc.rust-lang.org/cargo/reference/profiles.html": "ffc",
|
||||||
|
"https://doc.rust-lang.org/cargo/reference/specifying-dependencies.html": "f45",
|
||||||
|
"https://doc.rust-lang.org/nomicon/": "f6u",
|
||||||
|
"https://doc.rust-lang.org/reference/expressions/operator-expr.html#numeric-cast": "f2z",
|
||||||
|
"https://doc.rust-lang.org/reference/items/implementations.html#trait-implementation-coherence": "fzf",
|
||||||
|
"https://doc.rust-lang.org/reference/lifetime-elision.html": "f4c",
|
||||||
|
"https://doc.rust-lang.org/std/cell/struct.UnsafeCell.html": "fxy",
|
||||||
|
"https://doc.rust-lang.org/std/cmp/index.html": "fzm",
|
||||||
|
"https://doc.rust-lang.org/std/cmp/trait.PartialEq.html": "fzz",
|
||||||
|
"https://doc.rust-lang.org/std/cmp/trait.PartialOrd.html": "fzb",
|
||||||
|
"https://doc.rust-lang.org/std/convert/trait.From.html#implementors": "fzp",
|
||||||
|
"https://doc.rust-lang.org/std/convert/trait.Into.html#implementors": "fzl",
|
||||||
|
"https://doc.rust-lang.org/std/iter/trait.FusedIterator.html": "f4s",
|
||||||
|
"https://doc.rust-lang.org/std/iter/trait.Iterator.html": "fxf",
|
||||||
|
"https://doc.rust-lang.org/std/keyword.for.html": "ffj",
|
||||||
|
"https://doc.rust-lang.org/std/keyword.while.html": "ffh",
|
||||||
|
"https://doc.rust-lang.org/std/macro.panic.html": "ffl",
|
||||||
|
"https://doc.rust-lang.org/std/mem/fn.size_of.html": "f27",
|
||||||
|
"https://doc.rust-lang.org/std/ops/index.html": "fzn",
|
||||||
|
"https://doc.rust-lang.org/std/ops/trait.Add.html": "fz4",
|
||||||
|
"https://doc.rust-lang.org/std/ops/trait.Deref.html#deref-coercion": "fzt",
|
||||||
|
"https://doc.rust-lang.org/std/ops/trait.Div.html": "fzv",
|
||||||
|
"https://doc.rust-lang.org/std/ops/trait.Mul.html": "fz6",
|
||||||
|
"https://doc.rust-lang.org/std/ops/trait.Rem.html": "fz8",
|
||||||
|
"https://doc.rust-lang.org/std/ops/trait.Sub.html": "fzx",
|
||||||
|
"https://doc.rust-lang.org/std/prelude/index.html": "f2c",
|
||||||
|
"https://doc.rust-lang.org/std/primitive.i32.html#associatedconstant.MAX": "ffe",
|
||||||
|
"https://doc.rust-lang.org/std/primitive.i32.html#associatedconstant.MIN": "ff7",
|
||||||
|
"https://doc.rust-lang.org/std/primitive.u32.html#associatedconstant.MAX": "ffw",
|
||||||
|
"https://doc.rust-lang.org/std/slice/struct.Iter.html": "f4d",
|
||||||
|
"https://doc.rust-lang.org/std/string/struct.String.html": "f26",
|
||||||
|
"https://doc.rust-lang.org/std/sync/atomic/index.html": "fxh",
|
||||||
|
"https://doc.rust-lang.org/std/vec/struct.Vec.html#method.iter": "f4j",
|
||||||
|
"https://docs.rs/dhat/latest/dhat/": "f2y",
|
||||||
|
"https://docs.rs/itertools/": "fx2",
|
||||||
|
"https://docs.rs/thiserror/latest/thiserror/": "f4n",
|
||||||
|
"https://docs.rs/tokio-stream/latest/tokio_stream/": "f65",
|
||||||
|
"https://docs.rs/tokio-stream/latest/tokio_stream/trait.StreamExt.html#method.merge": "f6m",
|
||||||
|
"https://docs.rs/tokio-util/latest/tokio_util/sync/struct.CancellationToken.html": "f63",
|
||||||
|
"https://docs.rs/tokio/latest/tokio/task/struct.JoinError.html": "f6z",
|
||||||
|
"https://docs.rust-embedded.org/book/": "f6k",
|
||||||
|
"https://en.wikipedia.org/wiki/Dangling_pointer": "f2h",
|
||||||
|
"https://en.wikipedia.org/wiki/Data_segment": "fx7",
|
||||||
|
"https://en.wikipedia.org/wiki/Memory_address": "f2r",
|
||||||
|
"https://en.wikipedia.org/wiki/Stack_overflow": "f2e",
|
||||||
|
"https://en.wikipedia.org/wiki/Two%27s_complement": "ff9",
|
||||||
|
"https://en.wikipedia.org/wiki/UTF-8": "f2v",
|
||||||
|
"https://exercism.io": "f6r",
|
||||||
|
"https://github.com/LukeMathWalker/cargo-chef": "ffb",
|
||||||
|
"https://github.com/LukeMathWalker/wiremock-rs": "ffm",
|
||||||
|
"https://github.com/dtolnay/cargo-expand": "fzq",
|
||||||
|
"https://github.com/dtolnay/proc-macro-workshop": "fzw",
|
||||||
|
"https://github.com/mainmatter/100-exercises-to-learn-rust": "ff6",
|
||||||
|
"https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/01_intro/00_welcome": "ff3",
|
||||||
|
"https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/01_intro/01_syntax": "ffq",
|
||||||
|
"https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/02_basic_calculator/00_intro": "ff5",
|
||||||
|
"https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/02_basic_calculator/01_integers": "fft",
|
||||||
|
"https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/02_basic_calculator/02_variables": "ffy",
|
||||||
|
"https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/02_basic_calculator/03_if_else": "ffu",
|
||||||
|
"https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/02_basic_calculator/04_panics": "ffk",
|
||||||
|
"https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/02_basic_calculator/05_factorial": "ffs",
|
||||||
|
"https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/02_basic_calculator/06_while": "ffg",
|
||||||
|
"https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/02_basic_calculator/07_for": "ffd",
|
||||||
|
"https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/02_basic_calculator/08_overflow": "f2f",
|
||||||
|
"https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/02_basic_calculator/09_saturating": "f22",
|
||||||
|
"https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/02_basic_calculator/10_as_casting": "f24",
|
||||||
|
"https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/03_ticket_v1/00_intro": "f2x",
|
||||||
|
"https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/03_ticket_v1/01_struct": "f28",
|
||||||
|
"https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/03_ticket_v1/02_validation": "f2b",
|
||||||
|
"https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/03_ticket_v1/03_modules": "f2m",
|
||||||
|
"https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/03_ticket_v1/04_visibility": "f23",
|
||||||
|
"https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/03_ticket_v1/05_encapsulation": "f2q",
|
||||||
|
"https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/03_ticket_v1/06_ownership": "f25",
|
||||||
|
"https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/03_ticket_v1/07_setters": "f2w",
|
||||||
|
"https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/03_ticket_v1/08_stack": "f29",
|
||||||
|
"https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/03_ticket_v1/09_heap": "f2p",
|
||||||
|
"https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/03_ticket_v1/10_references_in_memory": "f2l",
|
||||||
|
"https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/03_ticket_v1/11_destructor": "f2g",
|
||||||
|
"https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/03_ticket_v1/12_outro": "f2j",
|
||||||
|
"https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/04_traits/00_intro": "f2d",
|
||||||
|
"https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/04_traits/01_trait": "f2a",
|
||||||
|
"https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/04_traits/02_orphan_rule": "fz2",
|
||||||
|
"https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/04_traits/03_operator_overloading": "fz3",
|
||||||
|
"https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/04_traits/04_derive": "fz7",
|
||||||
|
"https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/04_traits/05_trait_bounds": "fz9",
|
||||||
|
"https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/04_traits/06_str_slice": "fzr",
|
||||||
|
"https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/04_traits/07_deref": "fzy",
|
||||||
|
"https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/04_traits/08_sized": "fzu",
|
||||||
|
"https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/04_traits/09_from": "fzk",
|
||||||
|
"https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/04_traits/10_assoc_vs_generic": "fzs",
|
||||||
|
"https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/04_traits/11_clone": "fzh",
|
||||||
|
"https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/04_traits/12_copy": "fzg",
|
||||||
|
"https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/04_traits/13_drop": "fzj",
|
||||||
|
"https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/04_traits/14_outro": "fzc",
|
||||||
|
"https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/05_ticket_v2/00_intro": "fza",
|
||||||
|
"https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/05_ticket_v2/01_enum": "f4f",
|
||||||
|
"https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/05_ticket_v2/02_match": "f42",
|
||||||
|
"https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/05_ticket_v2/03_variants_with_data": "f4z",
|
||||||
|
"https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/05_ticket_v2/04_if_let": "f44",
|
||||||
|
"https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/05_ticket_v2/05_nullability": "f4x",
|
||||||
|
"https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/05_ticket_v2/06_fallibility": "f46",
|
||||||
|
"https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/05_ticket_v2/07_unwrap": "f4v",
|
||||||
|
"https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/05_ticket_v2/08_error_enums": "f48",
|
||||||
|
"https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/05_ticket_v2/09_error_trait": "f4b",
|
||||||
|
"https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/05_ticket_v2/10_packages": "f43",
|
||||||
|
"https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/05_ticket_v2/11_dependencies": "f4w",
|
||||||
|
"https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/05_ticket_v2/12_thiserror": "f47",
|
||||||
|
"https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/05_ticket_v2/13_try_from": "f4e",
|
||||||
|
"https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/05_ticket_v2/14_source": "f49",
|
||||||
|
"https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/05_ticket_v2/15_outro": "f4y",
|
||||||
|
"https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/06_ticket_management/00_intro": "f4u",
|
||||||
|
"https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/06_ticket_management/01_arrays": "f4p",
|
||||||
|
"https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/06_ticket_management/02_vec": "f4l",
|
||||||
|
"https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/06_ticket_management/03_resizing": "f4k",
|
||||||
|
"https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/06_ticket_management/04_iterators": "f4h",
|
||||||
|
"https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/06_ticket_management/05_iter": "f4g",
|
||||||
|
"https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/06_ticket_management/06_lifetimes": "f4a",
|
||||||
|
"https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/06_ticket_management/07_combinators": "fxz",
|
||||||
|
"https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/06_ticket_management/08_impl_trait": "fx4",
|
||||||
|
"https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/06_ticket_management/09_impl_trait_2": "fxx",
|
||||||
|
"https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/06_ticket_management/10_slices": "fx6",
|
||||||
|
"https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/06_ticket_management/11_mutable_slices": "fxv",
|
||||||
|
"https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/06_ticket_management/12_two_states": "fx8",
|
||||||
|
"https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/06_ticket_management/13_index": "fxb",
|
||||||
|
"https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/06_ticket_management/14_index_mut": "fxn",
|
||||||
|
"https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/06_ticket_management/15_hashmap": "fxm",
|
||||||
|
"https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/06_ticket_management/16_btreemap": "fx3",
|
||||||
|
"https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/07_threads/00_intro": "fxq",
|
||||||
|
"https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/07_threads/01_threads": "fxw",
|
||||||
|
"https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/07_threads/02_static": "fxe",
|
||||||
|
"https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/07_threads/03_leak": "fx9",
|
||||||
|
"https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/07_threads/04_scoped_threads": "fxr",
|
||||||
|
"https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/07_threads/05_channels": "fxt",
|
||||||
|
"https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/07_threads/06_interior_mutability": "fxu",
|
||||||
|
"https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/07_threads/07_ack": "fxp",
|
||||||
|
"https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/07_threads/08_client": "fxl",
|
||||||
|
"https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/07_threads/09_bounded": "fxk",
|
||||||
|
"https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/07_threads/10_patch": "fxs",
|
||||||
|
"https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/07_threads/11_locks": "fxj",
|
||||||
|
"https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/07_threads/12_rw_lock": "fxd",
|
||||||
|
"https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/07_threads/13_without_channels": "fxc",
|
||||||
|
"https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/07_threads/14_sync": "fxa",
|
||||||
|
"https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/08_futures/00_intro": "f6f",
|
||||||
|
"https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/08_futures/01_async_fn": "f62",
|
||||||
|
"https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/08_futures/02_spawn": "f64",
|
||||||
|
"https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/08_futures/03_runtime": "f6x",
|
||||||
|
"https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/08_futures/04_future": "f66",
|
||||||
|
"https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/08_futures/05_blocking": "f68",
|
||||||
|
"https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/08_futures/06_async_aware_primitives": "f6b",
|
||||||
|
"https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/08_futures/07_cancellation": "f6q",
|
||||||
|
"https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/08_futures/08_outro": "f6e",
|
||||||
|
"https://github.com/mainmatter/100-exercises-to-learn-rust/tree/solutions": "ffz",
|
||||||
|
"https://github.com/mainmatter/rust-advanced-testing-workshop": "fzd",
|
||||||
|
"https://github.com/rust-lang/rustlings": "f69",
|
||||||
|
"https://huonw.github.io/blog/2016/04/myths-and-legends-about-integer-overflow-in-rust/": "ffa",
|
||||||
|
"https://lexi-lambda.github.io/blog/2019/11/05/parse-don-t-validate/": "f4r",
|
||||||
|
"https://mainmatter.com/contact/": "ff2",
|
||||||
|
"https://mainmatter.com/rust-consulting/": "fff",
|
||||||
|
"https://marabos.nl/atomics/": "fxg",
|
||||||
|
"https://nostarch.com/rust-rustaceans": "f6p",
|
||||||
|
"https://owasp.org/www-community/vulnerabilities/Doubly_freeing_memory": "f2k",
|
||||||
|
"https://owasp.org/www-community/vulnerabilities/Using_freed_memory": "f2s",
|
||||||
|
"https://pavex.dev": "ffn",
|
||||||
|
"https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=36e5ddbe3b3f741dfa9f74c956622bac": "ffp",
|
||||||
|
"https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=afedf7062298ca8f5a248bc551062eaa": "fx5",
|
||||||
|
"https://rust-exercises.com/100-exercises-to-learn-rust.pdf": "ffx",
|
||||||
|
"https://rust-exercises.com/100-exercises/": "ff4",
|
||||||
|
"https://rust-exercises.com/advanced-testing/": "f6s",
|
||||||
|
"https://rust-exercises.com/telemetry/": "f6h",
|
||||||
|
"https://rust-lang.github.io/api-guidelines/naming.html#casing-conforms-to-rfc-430-c-case": "fze",
|
||||||
|
"https://rust-lang.github.io/wg-async/vision/submitted_stories/status_quo/barbara_battles_buffered_streams.html": "f6w",
|
||||||
|
"https://ryhl.io/blog/async-what-is-blocking/": "f6v",
|
||||||
|
"https://tokio.rs/tokio/tutorial/select": "f6n",
|
||||||
|
"https://valgrind.org/docs/manual/dh-manual.html": "f2t",
|
||||||
|
"https://veykril.github.io/tlborm/": "fz5",
|
||||||
|
"https://without.boats/blog/the-scoped-task-trilemma/": "f67",
|
||||||
|
"https://www.amazon.com/dp/B0DJ14KQQG/": "f6g",
|
||||||
|
"https://www.lpalmieri.com/": "ffv",
|
||||||
|
"https://www.lpalmieri.com/posts/2020-12-11-zero-to-production-6-domain-modelling/": "f4t",
|
||||||
|
"https://www.oreilly.com/library/view/programming-rust-2nd/9781492052586/": "f6y",
|
||||||
|
"https://www.youtube.com/playlist?list=PLqbS7AVVErFirH9armw8yXlE6dacF-A6z": "f6l",
|
||||||
|
"https://zero2prod.com": "ff8"
|
||||||
|
}
|
||||||
4
book/metadata.yml
Normal file
4
book/metadata.yml
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
title: "100 Exercises To Learn Rust"
|
||||||
|
subtitle: "A hands-on course by Mainmatter"
|
||||||
|
author: "Luca Palmieri"
|
||||||
|
keywords: ["Rust", "Programming"]
|
||||||
@@ -32,6 +32,11 @@ a mentor to help you along the way should you get stuck. You can
|
|||||||
also find solutions to all exercises in the
|
also find solutions to all exercises in the
|
||||||
[`solutions` branch of the GitHub repository](https://github.com/mainmatter/100-exercises-to-learn-rust/tree/solutions).
|
[`solutions` branch of the GitHub repository](https://github.com/mainmatter/100-exercises-to-learn-rust/tree/solutions).
|
||||||
|
|
||||||
|
## Formats
|
||||||
|
|
||||||
|
You can go through the course material [in the browser](https://rust-exercises.com/100-exercises/) or [download it as a PDF file](https://rust-exercises.com/100-exercises-to-learn-rust.pdf), for offline reading.\
|
||||||
|
If you prefer to have the course material printed out, [buy a paperback copy on Amazon](https://www.amazon.com/dp/B0DJ14KQQG/).
|
||||||
|
|
||||||
## Structure
|
## Structure
|
||||||
|
|
||||||
On the left side of the screen, you can see that the course is divided into sections.
|
On the left side of the screen, you can see that the course is divided into sections.
|
||||||
@@ -46,8 +51,7 @@ Before starting the course, make sure to clone the repository to your local mach
|
|||||||
# If you have an SSH key set up with GitHub
|
# If you have an SSH key set up with GitHub
|
||||||
git clone git@github.com:mainmatter/100-exercises-to-learn-rust.git
|
git clone git@github.com:mainmatter/100-exercises-to-learn-rust.git
|
||||||
# Otherwise, use the HTTPS URL:
|
# Otherwise, use the HTTPS URL:
|
||||||
#
|
# https://github.com/mainmatter/100-exercises-to-learn-rust.git
|
||||||
# git clone https://github.com/mainmatter/100-exercises-to-learn-rust.git
|
|
||||||
```
|
```
|
||||||
|
|
||||||
We also recommend you work on a branch, so you can easily track your progress and pull
|
We also recommend you work on a branch, so you can easily track your progress and pull
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ The function's body is enclosed in curly braces `{}`.
|
|||||||
In previous exercise, you saw the `greeting` function:
|
In previous exercise, you saw the `greeting` function:
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
// `fn` <function_name> ( <input parameters> ) -> <return_type> { <body> }
|
// `fn` <function_name> ( <input params> ) -> <return_type> { <body> }
|
||||||
fn greeting() -> &'static str {
|
fn greeting() -> &'static str {
|
||||||
// TODO: fix me 👇
|
// TODO: fix me 👇
|
||||||
"I'm ready to __!"
|
"I'm ready to __!"
|
||||||
|
|||||||
@@ -69,12 +69,18 @@ You may be wondering—what is a profile setting? Let's get into that!
|
|||||||
A [**profile**](https://doc.rust-lang.org/cargo/reference/profiles.html) is a set of configuration options that can be
|
A [**profile**](https://doc.rust-lang.org/cargo/reference/profiles.html) is a set of configuration options that can be
|
||||||
used to customize the way Rust code is compiled.
|
used to customize the way Rust code is compiled.
|
||||||
|
|
||||||
Cargo provides two built-in profiles: `dev` and `release`.\
|
Cargo provides 4 built-in profiles: `dev`, `release`, `test`, and `bench`.\
|
||||||
The `dev` profile is used every time you run `cargo build`, `cargo run` or `cargo test`. It's aimed at local
|
The `dev` profile is used every time you run `cargo build`, `cargo run` or `cargo test`. It's aimed at local
|
||||||
development,
|
development,
|
||||||
therefore it sacrifices runtime performance in favor of faster compilation times and a better debugging experience.\
|
therefore it sacrifices runtime performance in favor of faster compilation times and a better debugging experience.\
|
||||||
The `release` profile, instead, is optimized for runtime performance but incurs longer compilation times. You need
|
The `release` profile, instead, is optimized for runtime performance but incurs longer compilation times. You need
|
||||||
to explicitly request via the `--release` flag—e.g. `cargo build --release` or `cargo run --release`.
|
to explicitly request via the `--release` flag—e.g. `cargo build --release` or `cargo run --release`.
|
||||||
|
The `test` profile is the default profile used by `cargo test`. The `test` profile inherits the settings form the `dev` profile.
|
||||||
|
The `bench` profile is the default profile used by `cargo bench`. The `bench` profile inherits from the `release` profile.
|
||||||
|
Use `dev` for iterative development and debugging, `release` for optimized production builds,\
|
||||||
|
`test` for correctness testing, and `bench` for performance benchmarking.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
> "Have you built your project in release mode?" is almost a meme in the Rust community.\
|
> "Have you built your project in release mode?" is almost a meme in the Rust community.\
|
||||||
> It refers to developers who are not familiar with Rust and complain about its performance on
|
> It refers to developers who are not familiar with Rust and complain about its performance on
|
||||||
|
|||||||
@@ -72,7 +72,8 @@ error: literal out of range for `i8`
|
|||||||
4 | let a = 255 as i8;
|
4 | let a = 255 as i8;
|
||||||
| ^^^
|
| ^^^
|
||||||
|
|
|
|
||||||
= note: the literal `255` does not fit into the type `i8` whose range is `-128..=127`
|
= note: the literal `255` does not fit into the type `i8`
|
||||||
|
whose range is `-128..=127`
|
||||||
= help: consider using the type `u8` instead
|
= help: consider using the type `u8` instead
|
||||||
= note: `#[deny(overflowing_literals)]` on by default
|
= note: `#[deny(overflowing_literals)]` on by default
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ You can create an instance of a struct by specifying the values for each field:
|
|||||||
// Syntax: <StructName> { <field_name>: <value>, ... }
|
// Syntax: <StructName> { <field_name>: <value>, ... }
|
||||||
let ticket = Ticket {
|
let ticket = Ticket {
|
||||||
title: "Build a ticket system".into(),
|
title: "Build a ticket system".into(),
|
||||||
description: "Create a system that can manage tickets across a Kanban board".into(),
|
description: "A Kanban board".into(),
|
||||||
status: "Open".into()
|
status: "Open".into()
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
@@ -130,7 +130,8 @@ let default_config = Configuration::default();
|
|||||||
You can use the function call syntax even for methods that take `self` as their first parameter:
|
You can use the function call syntax even for methods that take `self` as their first parameter:
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
// Function call syntax: <StructName>::<method_name>(<instance>, <parameters>)
|
// Function call syntax:
|
||||||
|
// <StructName>::<method_name>(<instance>, <parameters>)
|
||||||
let is_open = Ticket::is_open(ticket);
|
let is_open = Ticket::is_open(ticket);
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ You have to use a **path** pointing to the entity you want to access.
|
|||||||
|
|
||||||
You can compose the path in various ways:
|
You can compose the path in various ways:
|
||||||
|
|
||||||
- starting from the root of the current crate, e.g. `crate::module_1::module_2::MyStruct`
|
- starting from the root of the current crate, e.g. `crate::module_1::MyStruct`
|
||||||
- starting from the parent module, e.g. `super::my_function`
|
- starting from the parent module, e.g. `super::my_function`
|
||||||
- starting from the current module, e.g. `sub_module_1::MyStruct`
|
- starting from the current module, e.g. `sub_module_1::MyStruct`
|
||||||
|
|
||||||
@@ -112,3 +112,10 @@ where each name comes from and potentially introducing name conflicts.\
|
|||||||
Nonetheless, it can be useful in some cases, like when writing unit tests. You might have noticed
|
Nonetheless, it can be useful in some cases, like when writing unit tests. You might have noticed
|
||||||
that most of our test modules start with a `use super::*;` statement to bring all the items from the parent module
|
that most of our test modules start with a `use super::*;` statement to bring all the items from the parent module
|
||||||
(the one being tested) into scope.
|
(the one being tested) into scope.
|
||||||
|
|
||||||
|
## Visualizing the module tree
|
||||||
|
|
||||||
|
If you're struggling to picture the module tree of your project, you can try using
|
||||||
|
[`cargo-modules`](https://crates.io/crates/cargo-modules) to visualize it!
|
||||||
|
|
||||||
|
Refer to their documentation for installation instructions and usage examples.
|
||||||
|
|||||||
@@ -23,14 +23,14 @@ To enforce stricter rules, we must keep the fields private[^newtype].
|
|||||||
We can then provide public methods to interact with a `Ticket` instance.
|
We can then provide public methods to interact with a `Ticket` instance.
|
||||||
Those public methods will have the responsibility of upholding our invariants (e.g. a title must not be empty).
|
Those public methods will have the responsibility of upholding our invariants (e.g. a title must not be empty).
|
||||||
|
|
||||||
If all fields are private, it is no longer possible to create a `Ticket` instance directly using the struct
|
If at least one field is private it is no longer possible to create a `Ticket` instance directly using the struct
|
||||||
instantiation syntax:
|
instantiation syntax:
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
// This won't work!
|
// This won't work!
|
||||||
let ticket = Ticket {
|
let ticket = Ticket {
|
||||||
title: "Build a ticket system".into(),
|
title: "Build a ticket system".into(),
|
||||||
description: "Create a system that can manage tickets across a Kanban board".into(),
|
description: "A Kanban board".into(),
|
||||||
status: "Open".into()
|
status: "Open".into()
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -44,9 +44,11 @@ error[E0382]: use of moved value: `ticket`
|
|||||||
| -------- `ticket` moved due to this method call
|
| -------- `ticket` moved due to this method call
|
||||||
...
|
...
|
||||||
30 | println!("Your next task is: {}", ticket.title());
|
30 | println!("Your next task is: {}", ticket.title());
|
||||||
| ^^^^^^ value used here after move
|
| ^^^^^^
|
||||||
|
| value used here after move
|
||||||
|
|
|
|
||||||
note: `Ticket::status` takes ownership of the receiver `self`, which moves `ticket`
|
note: `Ticket::status` takes ownership of the receiver `self`,
|
||||||
|
which moves `ticket`
|
||||||
--> src/main.rs:12:23
|
--> src/main.rs:12:23
|
||||||
|
|
|
|
||||||
12 | pub fn status(self) -> String {
|
12 | pub fn status(self) -> String {
|
||||||
@@ -95,8 +97,8 @@ Ownership can be transferred.
|
|||||||
If you own a value, for example, you can transfer ownership to another variable:
|
If you own a value, for example, you can transfer ownership to another variable:
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
let a = "hello, world".to_string(); // <--- `a` is the owner of the String
|
let a = "hello, world".to_string(); // <- `a` is the owner of the String
|
||||||
let b = a; // <--- `b` is now the owner of the String
|
let b = a; // <- `b` is now the owner of the String
|
||||||
```
|
```
|
||||||
|
|
||||||
Rust's ownership system is baked into the type system: each function has to declare in its signature
|
Rust's ownership system is baked into the type system: each function has to declare in its signature
|
||||||
@@ -130,9 +132,11 @@ error[E0382]: use of moved value: `ticket`
|
|||||||
| -------- `ticket` moved due to this method call
|
| -------- `ticket` moved due to this method call
|
||||||
...
|
...
|
||||||
30 | println!("Your next task is: {}", ticket.title());
|
30 | println!("Your next task is: {}", ticket.title());
|
||||||
| ^^^^^^ value used here after move
|
| ^^^^^^
|
||||||
|
| value used here after move
|
||||||
|
|
|
|
||||||
note: `Ticket::status` takes ownership of the receiver `self`, which moves `ticket`
|
note: `Ticket::status` takes ownership of the receiver `self`,
|
||||||
|
which moves `ticket`
|
||||||
--> src/main.rs:12:23
|
--> src/main.rs:12:23
|
||||||
|
|
|
|
||||||
12 | pub fn status(self) -> String {
|
12 | pub fn status(self) -> String {
|
||||||
@@ -199,8 +203,10 @@ fn main() {
|
|||||||
active: true,
|
active: true,
|
||||||
};
|
};
|
||||||
// `b` is a reference to the `version` field of `config`.
|
// `b` is a reference to the `version` field of `config`.
|
||||||
// The type of `b` is `&u32`, since it contains a reference to a `u32` value.
|
// The type of `b` is `&u32`, since it contains a reference to
|
||||||
// We create a reference by borrowing `config.version`, using the `&` operator.
|
// a `u32` value.
|
||||||
|
// We create a reference by borrowing `config.version`, using
|
||||||
|
// the `&` operator.
|
||||||
// Same symbol (`&`), different meaning depending on the context!
|
// Same symbol (`&`), different meaning depending on the context!
|
||||||
let b: &u32 = &config.version;
|
let b: &u32 = &config.version;
|
||||||
// ^ The type annotation is not necessary,
|
// ^ The type annotation is not necessary,
|
||||||
|
|||||||
@@ -50,7 +50,11 @@ It takes ownership of `self`, changes the title, and returns the modified `Ticke
|
|||||||
This is how you'd use it:
|
This is how you'd use it:
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
let ticket = Ticket::new("Title".into(), "Description".into(), "To-Do".into());
|
let ticket = Ticket::new(
|
||||||
|
"Title".into(),
|
||||||
|
"Description".into(),
|
||||||
|
"To-Do".into()
|
||||||
|
);
|
||||||
let ticket = ticket.set_title("New title".into());
|
let ticket = ticket.set_title("New title".into());
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -88,7 +92,11 @@ Nothing is returned.
|
|||||||
You'd use it like this:
|
You'd use it like this:
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
let mut ticket = Ticket::new("Title".into(), "Description".into(), "To-Do".into());
|
let mut ticket = Ticket::new(
|
||||||
|
"Title".into(),
|
||||||
|
"Description".into(),
|
||||||
|
"To-Do".into()
|
||||||
|
);
|
||||||
ticket.set_title("New title".into());
|
ticket.set_title("New title".into());
|
||||||
|
|
||||||
// Use the modified ticket
|
// Use the modified ticket
|
||||||
|
|||||||
@@ -18,11 +18,25 @@ the function's arguments, local variables and a few "bookkeeping" values.\
|
|||||||
When the function returns, the stack frame is popped off the stack[^stack-overflow].
|
When the function returns, the stack frame is popped off the stack[^stack-overflow].
|
||||||
|
|
||||||
```text
|
```text
|
||||||
+-----------------+
|
+-----------------+
|
||||||
func2 | frame for func2 | func2
|
| frame for func1 |
|
||||||
+-----------------+ is called +-----------------+ returns +-----------------+
|
+-----------------+
|
||||||
| frame for func1 | -----------> | frame for func1 | ---------> | frame for func1 |
|
|
|
||||||
+-----------------+ +-----------------+ +-----------------+
|
| func2 is
|
||||||
|
| called
|
||||||
|
v
|
||||||
|
+-----------------+
|
||||||
|
| frame for func2 |
|
||||||
|
+-----------------+
|
||||||
|
| frame for func1 |
|
||||||
|
+-----------------+
|
||||||
|
|
|
||||||
|
| func2
|
||||||
|
| returns
|
||||||
|
v
|
||||||
|
+-----------------+
|
||||||
|
| frame for func1 |
|
||||||
|
+-----------------+
|
||||||
```
|
```
|
||||||
|
|
||||||
From an operational point of view, stack allocation/de-allocation is **very fast**.\
|
From an operational point of view, stack allocation/de-allocation is **very fast**.\
|
||||||
|
|||||||
@@ -89,7 +89,8 @@ although a bit more cumbersome to read:
|
|||||||
impl ::core::cmp::PartialEq for Ticket {
|
impl ::core::cmp::PartialEq for Ticket {
|
||||||
#[inline]
|
#[inline]
|
||||||
fn eq(&self, other: &Ticket) -> bool {
|
fn eq(&self, other: &Ticket) -> bool {
|
||||||
self.title == other.title && self.description == other.description
|
self.title == other.title
|
||||||
|
&& self.description == other.description
|
||||||
&& self.status == other.status
|
&& self.status == other.status
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -94,11 +94,13 @@ fn print_if_even<T>(n: T) {
|
|||||||
This code won't compile:
|
This code won't compile:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
error[E0599]: no method named `is_even` found for type parameter `T` in the current scope
|
error[E0599]: no method named `is_even` found for type parameter `T`
|
||||||
|
in the current scope
|
||||||
--> src/lib.rs:2:10
|
--> src/lib.rs:2:10
|
||||||
|
|
|
|
||||||
1 | fn print_if_even<T>(n: T) {
|
1 | fn print_if_even<T>(n: T) {
|
||||||
| - method `is_even` not found for this type parameter
|
| - method `is_even` not found
|
||||||
|
| for this type parameter
|
||||||
2 | if n.is_even() {
|
2 | if n.is_even() {
|
||||||
| ^^^^^^^ method not found in `T`
|
| ^^^^^^^ method not found in `T`
|
||||||
|
|
||||||
@@ -106,7 +108,9 @@ error[E0277]: `T` doesn't implement `Debug`
|
|||||||
--> src/lib.rs:3:19
|
--> src/lib.rs:3:19
|
||||||
|
|
|
|
||||||
3 | println!("{n:?} is even");
|
3 | println!("{n:?} is even");
|
||||||
| ^^^^^ `T` cannot be formatted using `{:?}` because it doesn't implement `Debug`
|
| ^^^^^
|
||||||
|
| `T` cannot be formatted using `{:?}` because
|
||||||
|
| it doesn't implement `Debug`
|
||||||
|
|
|
|
||||||
help: consider restricting type parameter `T`
|
help: consider restricting type parameter `T`
|
||||||
|
|
|
|
||||||
|
|||||||
@@ -72,7 +72,8 @@ You can, for example, create a `&str` from a `String` like this:
|
|||||||
```rust
|
```rust
|
||||||
let mut s = String::with_capacity(5);
|
let mut s = String::with_capacity(5);
|
||||||
s.push_str("Hello");
|
s.push_str("Hello");
|
||||||
// Create a string slice reference from the `String`, skipping the first byte.
|
// Create a string slice reference from the `String`,
|
||||||
|
// skipping the first byte.
|
||||||
let slice: &str = &s[1..];
|
let slice: &str = &s[1..];
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,8 @@ to the pointer: the length of the slice it points to. Going back to the example
|
|||||||
```rust
|
```rust
|
||||||
let mut s = String::with_capacity(5);
|
let mut s = String::with_capacity(5);
|
||||||
s.push_str("Hello");
|
s.push_str("Hello");
|
||||||
// Create a string slice reference from the `String`, skipping the first byte.
|
// Create a string slice reference from the `String`,
|
||||||
|
// skipping the first byte.
|
||||||
let slice: &str = &s[1..];
|
let slice: &str = &s[1..];
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,11 @@
|
|||||||
Let's go back to where our string journey started:
|
Let's go back to where our string journey started:
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
let ticket = Ticket::new("A title".into(), "A description".into(), "To-Do".into());
|
let ticket = Ticket::new(
|
||||||
|
"A title".into(),
|
||||||
|
"A description".into(),
|
||||||
|
"To-Do".into()
|
||||||
|
);
|
||||||
```
|
```
|
||||||
|
|
||||||
We now know enough to start unpacking what `.into()` is doing here.
|
We now know enough to start unpacking what `.into()` is doing here.
|
||||||
@@ -14,7 +18,11 @@ This is the signature of the `new` method:
|
|||||||
|
|
||||||
```rust
|
```rust
|
||||||
impl Ticket {
|
impl Ticket {
|
||||||
pub fn new(title: String, description: String, status: String) -> Self {
|
pub fn new(
|
||||||
|
title: String,
|
||||||
|
description: String,
|
||||||
|
status: String
|
||||||
|
) -> Self {
|
||||||
// [...]
|
// [...]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -108,7 +116,7 @@ let title = String::from("A title");
|
|||||||
|
|
||||||
We've been primarily using `.into()`, though.\
|
We've been primarily using `.into()`, though.\
|
||||||
If you check out the [implementors of `Into`](https://doc.rust-lang.org/std/convert/trait.Into.html#implementors)
|
If you check out the [implementors of `Into`](https://doc.rust-lang.org/std/convert/trait.Into.html#implementors)
|
||||||
you won't find `Into<&str> for String`. What's going on?
|
you won't find `Into<String> for &str`. What's going on?
|
||||||
|
|
||||||
`From` and `Into` are **dual traits**.\
|
`From` and `Into` are **dual traits**.\
|
||||||
In particular, `Into` is implemented for any type that implements `From` using a **blanket implementation**:
|
In particular, `Into` is implemented for any type that implements `From` using a **blanket implementation**:
|
||||||
|
|||||||
@@ -44,7 +44,8 @@ impl Drop for MyType {
|
|||||||
The compiler will complain with this error message:
|
The compiler will complain with this error message:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
error[E0184]: the trait `Copy` cannot be implemented for this type; the type has a destructor
|
error[E0184]: the trait `Copy` cannot be implemented for this type;
|
||||||
|
the type has a destructor
|
||||||
--> src/lib.rs:2:17
|
--> src/lib.rs:2:17
|
||||||
|
|
|
|
||||||
2 | #[derive(Clone, Copy)]
|
2 | #[derive(Clone, Copy)]
|
||||||
|
|||||||
@@ -14,7 +14,11 @@ pub struct Ticket {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Ticket {
|
impl Ticket {
|
||||||
pub fn new(title: String, description: String, status: String) -> Self {
|
pub fn new(
|
||||||
|
title: String,
|
||||||
|
description: String,
|
||||||
|
status: String
|
||||||
|
) -> Self {
|
||||||
// [...]
|
// [...]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,10 @@ impl Ticket {
|
|||||||
match &self.status {
|
match &self.status {
|
||||||
Status::InProgress { assigned_to } => assigned_to,
|
Status::InProgress { assigned_to } => assigned_to,
|
||||||
Status::Done | Status::ToDo => {
|
Status::Done | Status::ToDo => {
|
||||||
panic!("Only `In-Progress` tickets can be assigned to someone"),
|
panic!(
|
||||||
|
"Only `In-Progress` tickets can be \
|
||||||
|
assigned to someone"
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -33,7 +36,9 @@ impl Ticket {
|
|||||||
if let Status::InProgress { assigned_to } = &self.status {
|
if let Status::InProgress { assigned_to } = &self.status {
|
||||||
assigned_to
|
assigned_to
|
||||||
} else {
|
} else {
|
||||||
panic!("Only `In-Progress` tickets can be assigned to someone");
|
panic!(
|
||||||
|
"Only `In-Progress` tickets can be assigned to someone"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -48,7 +53,9 @@ you can use the `let/else` construct:
|
|||||||
impl Ticket {
|
impl Ticket {
|
||||||
pub fn assigned_to(&self) -> &str {
|
pub fn assigned_to(&self) -> &str {
|
||||||
let Status::InProgress { assigned_to } = &self.status else {
|
let Status::InProgress { assigned_to } = &self.status else {
|
||||||
panic!("Only `In-Progress` tickets can be assigned to someone");
|
panic!(
|
||||||
|
"Only `In-Progress` tickets can be assigned to someone"
|
||||||
|
);
|
||||||
};
|
};
|
||||||
assigned_to
|
assigned_to
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,11 @@ Let's revisit the `Ticket::new` function from the previous exercise:
|
|||||||
|
|
||||||
```rust
|
```rust
|
||||||
impl Ticket {
|
impl Ticket {
|
||||||
pub fn new(title: String, description: String, status: Status) -> Ticket {
|
pub fn new(
|
||||||
|
title: String,
|
||||||
|
description: String,
|
||||||
|
status: Status
|
||||||
|
) -> Ticket {
|
||||||
if title.is_empty() {
|
if title.is_empty() {
|
||||||
panic!("Title cannot be empty");
|
panic!("Title cannot be empty");
|
||||||
}
|
}
|
||||||
@@ -70,8 +74,9 @@ Rust, with `Result`, forces you to **encode fallibility in the function's signat
|
|||||||
If a function can fail (and you want the caller to have a shot at handling the error), it must return a `Result`.
|
If a function can fail (and you want the caller to have a shot at handling the error), it must return a `Result`.
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
// Just by looking at the signature, you know that this function can fail.
|
// Just by looking at the signature, you know that this function
|
||||||
// You can also inspect `ParseIntError` to see what kind of failures to expect.
|
// can fail. You can also inspect `ParseIntError` to see what
|
||||||
|
// kind of failures to expect.
|
||||||
fn parse_int(s: &str) -> Result<i32, ParseIntError> {
|
fn parse_int(s: &str) -> Result<i32, ParseIntError> {
|
||||||
// ...
|
// ...
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,8 +14,8 @@ fn parse_int(s: &str) -> Result<i32, ParseIntError> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// This won't compile: we're not handling the error case.
|
// This won't compile: we're not handling the error case.
|
||||||
// We must either use `match` or one of the combinators provided by `Result`
|
// We must either use `match` or one of the combinators provided by
|
||||||
// to "unwrap" the success value or handle the error.
|
// `Result` to "unwrap" the success value or handle the error.
|
||||||
let number = parse_int("42") + 2;
|
let number = parse_int("42") + 2;
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ that implements the `Error` trait.
|
|||||||
pub trait Error: Debug + Display {}
|
pub trait Error: Debug + Display {}
|
||||||
```
|
```
|
||||||
|
|
||||||
You might recall the `:` syntax from [the `Sized` trait](../04_traits/08_sized.md)—it's used to specify **supertraits**.
|
You might recall the `:` syntax from [the `From` trait](../04_traits/09_from.md#supertrait--subtrait)—it's used to specify **supertraits**.
|
||||||
For `Error`, there are two supertraits: `Debug` and `Display`. If a type wants to implement `Error`, it must also
|
For `Error`, there are two supertraits: `Debug` and `Display`. If a type wants to implement `Error`, it must also
|
||||||
implement `Debug` and `Display`.
|
implement `Debug` and `Display`.
|
||||||
|
|
||||||
|
|||||||
@@ -46,18 +46,3 @@ You can override these defaults by explicitly declaring your targets in the `Car
|
|||||||
[`cargo`'s documentation](https://doc.rust-lang.org/cargo/reference/cargo-targets.html#cargo-targets) for more details.
|
[`cargo`'s documentation](https://doc.rust-lang.org/cargo/reference/cargo-targets.html#cargo-targets) for more details.
|
||||||
|
|
||||||
Keep in mind that while a package can contain multiple crates, it can only contain one library crate.
|
Keep in mind that while a package can contain multiple crates, it can only contain one library crate.
|
||||||
|
|
||||||
## Scaffolding a new package
|
|
||||||
|
|
||||||
You can use `cargo` to scaffold a new package:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cargo new my-binary
|
|
||||||
```
|
|
||||||
|
|
||||||
This will create a new folder, `my-binary`, containing a new Rust package with the same name and a single
|
|
||||||
binary crate inside. If you want to create a library crate instead, you can use the `--lib` flag:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cargo new my-library --lib
|
|
||||||
```
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ store and retrieve tickets.
|
|||||||
The task will give us an opportunity to explore new Rust concepts, such as:
|
The task will give us an opportunity to explore new Rust concepts, such as:
|
||||||
|
|
||||||
- Stack-allocated arrays
|
- Stack-allocated arrays
|
||||||
- `Vec`, a growable array type, and slices
|
- `Vec`, a growable array type
|
||||||
- `Iterator` and `IntoIterator`, for iterating over collections
|
- `Iterator` and `IntoIterator`, for iterating over collections
|
||||||
- Slices (`&[T]`), to work with parts of a collection
|
- Slices (`&[T]`), to work with parts of a collection
|
||||||
- Lifetimes, to describe how long references are valid
|
- Lifetimes, to describe how long references are valid
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# Combinators
|
# Combinators
|
||||||
|
|
||||||
Iterators can do so much more than `for` loops!\
|
Iterators can do so much more than `for` loops!\
|
||||||
If you look at the documentation for the `Iterator` trait, you'll find a **vast** collections of
|
If you look at the documentation for the `Iterator` trait, you'll find a **vast** collection of
|
||||||
methods that you can leverage to transform, filter, and combine iterators in various ways.
|
methods that you can leverage to transform, filter, and combine iterators in various ways.
|
||||||
|
|
||||||
Let's mention the most common ones:
|
Let's mention the most common ones:
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ and why we might want to use them.
|
|||||||
## What is a thread?
|
## What is a thread?
|
||||||
|
|
||||||
A **thread** is an execution context managed by the underlying operating system.\
|
A **thread** is an execution context managed by the underlying operating system.\
|
||||||
Each thread has its own stack, instruction pointer, and program counter.
|
Each thread has its own stack and instruction pointer.
|
||||||
|
|
||||||
A single **process** can manage multiple threads.
|
A single **process** can manage multiple threads.
|
||||||
These threads share the same memory space, which means they can access the same data.
|
These threads share the same memory space, which means they can access the same data.
|
||||||
|
|||||||
@@ -11,9 +11,9 @@ error[E0597]: `v` does not live long enough
|
|||||||
...
|
...
|
||||||
15 | let right = &v[split_point..];
|
15 | let right = &v[split_point..];
|
||||||
| ^ borrowed value does not live long enough
|
| ^ borrowed value does not live long enough
|
||||||
16 | let left_handle = thread::spawn(move || left.iter().sum::<i32>());
|
16 | let left_handle = spawn(move || left.iter().sum::<i32>());
|
||||||
| ------------------------------------------------
|
| --------------------------------
|
||||||
argument requires that `v` is borrowed for `'static`
|
argument requires that `v` is borrowed for `'static`
|
||||||
19 | }
|
19 | }
|
||||||
| - `v` dropped here while still borrowed
|
| - `v` dropped here while still borrowed
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -27,12 +27,12 @@ run out and crash with an out-of-memory error.
|
|||||||
fn oom_trigger() {
|
fn oom_trigger() {
|
||||||
loop {
|
loop {
|
||||||
let v: Vec<usize> = Vec::with_capacity(1024);
|
let v: Vec<usize> = Vec::with_capacity(1024);
|
||||||
Box::leak(v);
|
v.leak();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
At the same time, memory leaked via `Box::leak` is not truly forgotten.\
|
At the same time, memory leaked via `leak` method is not truly forgotten.\
|
||||||
The operating system can map each memory region to the process responsible for it.
|
The operating system can map each memory region to the process responsible for it.
|
||||||
When the process exits, the operating system will reclaim that memory.
|
When the process exits, the operating system will reclaim that memory.
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
So far we've been using unbounded channels.\
|
So far we've been using unbounded channels.\
|
||||||
You can send as many messages as you want, and the channel will grow to accommodate them.\
|
You can send as many messages as you want, and the channel will grow to accommodate them.\
|
||||||
In a multi-producer single-consumer scenario, this can be problematic: if the producers
|
In a multi-producer single-consumer scenario, this can be problematic: if the producers
|
||||||
enqueues messages at a faster rate than the consumer can process them, the channel will
|
enqueue messages at a faster rate than the consumer can process them, the channel will
|
||||||
keep growing, potentially consuming all available memory.
|
keep growing, potentially consuming all available memory.
|
||||||
|
|
||||||
Our recommendation is to **never** use an unbounded channel in a production system.\
|
Our recommendation is to **never** use an unbounded channel in a production system.\
|
||||||
|
|||||||
@@ -10,9 +10,9 @@ In the non-threaded version of the system, updates were fairly straightforward:
|
|||||||
|
|
||||||
## Multithreaded updates
|
## Multithreaded updates
|
||||||
|
|
||||||
The same strategy won't work in the current multi-threaded version,
|
The same strategy won't work in the current multithreaded version. The borrow checker would
|
||||||
because the mutable reference would have to be sent over a channel. The borrow checker would
|
stop us: `SyncSender<&mut Ticket>` isn't `'static` because `&mut Ticket` doesn't satisfy the `'static` lifetime, therefore
|
||||||
stop us, because `&mut Ticket` doesn't satisfy the `'static` lifetime requirement of `SyncSender::send`.
|
they can't be captured by the closure that gets passed to `std::thread::spawn`.
|
||||||
|
|
||||||
There are a few ways to work around this limitation. We'll explore a few of them in the following exercises.
|
There are a few ways to work around this limitation. We'll explore a few of them in the following exercises.
|
||||||
|
|
||||||
|
|||||||
@@ -99,7 +99,7 @@ fn main() {
|
|||||||
let guard = lock.lock().unwrap();
|
let guard = lock.lock().unwrap();
|
||||||
|
|
||||||
spawn(move || {
|
spawn(move || {
|
||||||
receiver.recv().unwrap();;
|
receiver.recv().unwrap();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Try to send the guard over the channel
|
// Try to send the guard over the channel
|
||||||
@@ -111,19 +111,23 @@ fn main() {
|
|||||||
The compiler is not happy with this code:
|
The compiler is not happy with this code:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
error[E0277]: `MutexGuard<'_, i32>` cannot be sent between threads safely
|
error[E0277]: `MutexGuard<'_, i32>` cannot be sent between
|
||||||
|
threads safely
|
||||||
--> src/main.rs:10:7
|
--> src/main.rs:10:7
|
||||||
|
|
|
|
||||||
10 | spawn(move || {
|
10 | spawn(move || {
|
||||||
| _-----_^
|
| _-----_^
|
||||||
| | |
|
| | |
|
||||||
| | required by a bound introduced by this call
|
| | required by a bound introduced by this call
|
||||||
11 | | receiver.recv().unwrap();;
|
11 | | receiver.recv().unwrap();
|
||||||
12 | | });
|
12 | | });
|
||||||
| |_^ `MutexGuard<'_, i32>` cannot be sent between threads safely
|
| |_^ `MutexGuard<'_, i32>` cannot be sent between threads safely
|
||||||
|
|
|
|
||||||
= help: the trait `Send` is not implemented for `MutexGuard<'_, i32>`, which is required by `{closure@src/main.rs:10:7: 10:14}: Send`
|
= help: the trait `Send` is not implemented for
|
||||||
= note: required for `std::sync::mpsc::Receiver<MutexGuard<'_, i32>>` to implement `Send`
|
`MutexGuard<'_, i32>`, which is required by
|
||||||
|
`{closure@src/main.rs:10:7: 10:14}: Send`
|
||||||
|
= note: required for `Receiver<MutexGuard<'_, i32>>`
|
||||||
|
to implement `Send`
|
||||||
note: required because it's used within this closure
|
note: required because it's used within this closure
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -107,7 +107,7 @@ asynchronous programming in Rust.
|
|||||||
The entrypoint of your executable, the `main` function, must be a synchronous function.
|
The entrypoint of your executable, the `main` function, must be a synchronous function.
|
||||||
That's where you're supposed to set up and launch your chosen async runtime.
|
That's where you're supposed to set up and launch your chosen async runtime.
|
||||||
|
|
||||||
Most runtimes provides a macro to make this easier. For `tokio`, it's `tokio::main`:
|
Most runtimes provide a macro to make this easier. For `tokio`, it's `tokio::main`:
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
|
|||||||
@@ -112,7 +112,7 @@ pub async fn work() {
|
|||||||
|
|
||||||
### `std::thread::spawn` vs `tokio::spawn`
|
### `std::thread::spawn` vs `tokio::spawn`
|
||||||
|
|
||||||
You can think of `tokio::spawn` as the asynchronous sibling of `std::spawn::thread`.
|
You can think of `tokio::spawn` as the asynchronous sibling of `std::thread::spawn`.
|
||||||
|
|
||||||
Notice a key difference: with `std::thread::spawn`, you're delegating control to the OS scheduler.
|
Notice a key difference: with `std::thread::spawn`, you're delegating control to the OS scheduler.
|
||||||
You're not in control of how threads are scheduled.
|
You're not in control of how threads are scheduled.
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ across threads.
|
|||||||
## Implications
|
## Implications
|
||||||
|
|
||||||
`tokio::spawn` is flavor-agnostic: it'll work no matter if you're running on the multithreaded
|
`tokio::spawn` is flavor-agnostic: it'll work no matter if you're running on the multithreaded
|
||||||
or current-thread runtime. The downside is that the signature assume the worst case
|
or current-thread runtime. The downside is that the signature assumes the worst case
|
||||||
(i.e. multithreaded) and is constrained accordingly:
|
(i.e. multithreaded) and is constrained accordingly:
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
|
|||||||
@@ -47,7 +47,8 @@ The compiler will reject this code:
|
|||||||
error: future cannot be sent between threads safely
|
error: future cannot be sent between threads safely
|
||||||
|
|
|
|
||||||
5 | tokio::spawn(example());
|
5 | tokio::spawn(example());
|
||||||
| ^^^^^^^^^ future returned by `example` is not `Send`
|
| ^^^^^^^^^
|
||||||
|
| future returned by `example` is not `Send`
|
||||||
|
|
|
|
||||||
note: future is not `Send` as this value is used across an await
|
note: future is not `Send` as this value is used across an await
|
||||||
|
|
|
|
||||||
@@ -55,7 +56,8 @@ note: future is not `Send` as this value is used across an await
|
|||||||
| -------- has type `Rc<i32>` which is not `Send`
|
| -------- has type `Rc<i32>` which is not `Send`
|
||||||
12 | // A `.await` point
|
12 | // A `.await` point
|
||||||
13 | yield_now().await;
|
13 | yield_now().await;
|
||||||
| ^^^^^ await occurs here, with `non_send` maybe used later
|
| ^^^^^
|
||||||
|
| await occurs here, with `non_send` maybe used later
|
||||||
note: required by a bound in `tokio::spawn`
|
note: required by a bound in `tokio::spawn`
|
||||||
|
|
|
|
||||||
164 | pub fn spawn<F>(future: F) -> JoinHandle<F::Output>
|
164 | pub fn spawn<F>(future: F) -> JoinHandle<F::Output>
|
||||||
@@ -84,7 +86,10 @@ trait Future {
|
|||||||
type Output;
|
type Output;
|
||||||
|
|
||||||
// Ignore `Pin` and `Context` for now
|
// Ignore `Pin` and `Context` for now
|
||||||
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output>;
|
fn poll(
|
||||||
|
self: Pin<&mut Self>,
|
||||||
|
cx: &mut Context<'_>
|
||||||
|
) -> Poll<Self::Output>;
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ Yields to runtime
|
|||||||
Tries to acquire lock
|
Tries to acquire lock
|
||||||
```
|
```
|
||||||
|
|
||||||
We have a deadlock. Task B we'll never manage to acquire the lock, because the lock
|
We have a deadlock. Task B will never manage to acquire the lock, because the lock
|
||||||
is currently held by task A, which has yielded to the runtime before releasing the
|
is currently held by task A, which has yielded to the runtime before releasing the
|
||||||
lock and won't be scheduled again because the runtime cannot preempt task B.
|
lock and won't be scheduled again because the runtime cannot preempt task B.
|
||||||
|
|
||||||
|
|||||||
@@ -105,5 +105,5 @@ async fn run() {
|
|||||||
Check out [`select!`'s documentation](https://tokio.rs/tokio/tutorial/select) for more details.\
|
Check out [`select!`'s documentation](https://tokio.rs/tokio/tutorial/select) for more details.\
|
||||||
If you need to interleave two asynchronous streams of data (e.g. a socket and a channel), prefer using
|
If you need to interleave two asynchronous streams of data (e.g. a socket and a channel), prefer using
|
||||||
[`StreamExt::merge`](https://docs.rs/tokio-stream/latest/tokio_stream/trait.StreamExt.html#method.merge) instead.
|
[`StreamExt::merge`](https://docs.rs/tokio-stream/latest/tokio_stream/trait.StreamExt.html#method.merge) instead.
|
||||||
- Rather than "abrupt" cancellation, it can be preferable to rely
|
- A [`CancellationToken`](https://docs.rs/tokio-util/latest/tokio_util/sync/struct.CancellationToken.html) may be
|
||||||
on [`CancellationToken`](https://docs.rs/tokio-util/latest/tokio_util/sync/struct.CancellationToken.html).
|
preferable to `JoinHandle::abort` in some cases.
|
||||||
|
|||||||
@@ -48,5 +48,5 @@ check out the [Embedded Rust book](https://docs.rust-embedded.org/book/).
|
|||||||
|
|
||||||
You can then find resources on key topics that cut across domains.\
|
You can then find resources on key topics that cut across domains.\
|
||||||
For testing, check out
|
For testing, check out
|
||||||
["Advanced testing, going beyond the basics"](https://github.com/mainmatter/rust-advanced-testing-workshop).\
|
["Advanced testing, going beyond the basics"](https://rust-exercises.com/advanced-testing/).\
|
||||||
For telemetry, check out ["You can't fix what you can't see"](https://github.com/mainmatter/rust-telemetry-workshop).
|
For telemetry, check out ["You can't fix what you can't see"](https://rust-exercises.com/telemetry/).
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ mod tests {
|
|||||||
// 👇 With the `#[should_panic]` annotation we can assert that we expect the code
|
// 👇 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`.
|
// 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!
|
// This is all part of Rust's built-in test framework!
|
||||||
#[should_panic(expected = "The journey took no time at all, that's impossible!")]
|
#[should_panic(expected = "The journey took no time at all. That's impossible!")]
|
||||||
fn by_zero() {
|
fn by_zero() {
|
||||||
speed(0, 10, 0);
|
speed(0, 10, 0);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ mod tests {
|
|||||||
|
|
||||||
// You could solve this by using exactly the same expression as above,
|
// 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
|
// but that would defeat the purpose of the exercise. Instead, use a genuine
|
||||||
// `i8` value that is equivalent to `255` when converted from `u8`.
|
// `i8` value that is equivalent to `255` when converted to `u8`.
|
||||||
let y: i8 = todo!();
|
let y: i8 = todo!();
|
||||||
|
|
||||||
assert_eq!(x, y);
|
assert_eq!(x, y);
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ impl Ticket {
|
|||||||
// - the `title` should be at most 50 bytes long.
|
// - the `title` should be at most 50 bytes long.
|
||||||
// - the `description` should be at most 500 bytes long.
|
// - the `description` should be at most 500 bytes long.
|
||||||
// The method should panic if any of the requirements are not met.
|
// 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,
|
// 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
|
// as well as some `String` methods. Use the documentation of Rust's standard library
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ mod tests {
|
|||||||
|
|
||||||
// You should be seeing this error when trying to run this exercise:
|
// You should be seeing this error when trying to run this exercise:
|
||||||
//
|
//
|
||||||
// error[E0616]: field `description` of struct `encapsulation::ticket::Ticket` is private
|
// error[E0616]: field `description` of struct `Ticket` is private
|
||||||
// |
|
// |
|
||||||
// | assert_eq!(ticket.description, "A description");
|
// | assert_eq!(ticket.description, "A description");
|
||||||
// | ^^^^^^^^^^^^^^^^^^
|
// | ^^^^^^^^^^^^^^^^^^
|
||||||
|
|||||||
@@ -21,8 +21,8 @@ mod tests {
|
|||||||
// This is a tricky question!
|
// This is a tricky question!
|
||||||
// The "intuitive" answer happens to be the correct answer this time,
|
// The "intuitive" answer happens to be the correct answer this time,
|
||||||
// but, in general, the memory layout of structs is a more complex topic.
|
// but, in general, the memory layout of structs is a more complex topic.
|
||||||
// If you're curious, check out the "Data layout" section of the Rustonomicon
|
// If you're curious, check out the "Type layout" section of The Rust Reference
|
||||||
// https://doc.rust-lang.org/nomicon/data.html for more information.
|
// https://doc.rust-lang.org/reference/type-layout.html for more information.
|
||||||
assert_eq!(size_of::<Ticket>(), todo!());
|
assert_eq!(size_of::<Ticket>(), todo!());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,11 +6,12 @@ fn test_saturating_u16() {
|
|||||||
let b: SaturatingU16 = 5u8.into();
|
let b: SaturatingU16 = 5u8.into();
|
||||||
let c: SaturatingU16 = u16::MAX.into();
|
let c: SaturatingU16 = u16::MAX.into();
|
||||||
let d: SaturatingU16 = (&1u16).into();
|
let d: SaturatingU16 = (&1u16).into();
|
||||||
|
let e = &c;
|
||||||
|
|
||||||
assert_eq!(a + b, SaturatingU16::from(15u16));
|
assert_eq!(a + b, SaturatingU16::from(15u16));
|
||||||
assert_eq!(a + c, SaturatingU16::from(u16::MAX));
|
assert_eq!(a + c, SaturatingU16::from(u16::MAX));
|
||||||
assert_eq!(a + d, SaturatingU16::from(11u16));
|
assert_eq!(a + d, SaturatingU16::from(11u16));
|
||||||
assert_eq!(a + a, 20u16);
|
assert_eq!(a + a, 20u16);
|
||||||
assert_eq!(a + 5u16, 15u16);
|
assert_eq!(a + 5u16, 15u16);
|
||||||
assert_eq!(a + &u16::MAX, SaturatingU16::from(u16::MAX));
|
assert_eq!(a + e, SaturatingU16::from(u16::MAX));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// TODO: Implement `TryFrom<String>` and `TryFrom<&str>` for the `TicketTitle` type,
|
// TODO: Implement `TryFrom<String>` and `TryFrom<&str>` for the `TicketTitle` type,
|
||||||
// enforcing that the title is not empty and is not longer than 50 characters.
|
// enforcing that the title is not empty and is not longer than 50 bytes.
|
||||||
// Implement the traits required to make the tests pass too.
|
// Implement the traits required to make the tests pass too.
|
||||||
|
|
||||||
pub struct TicketTitle(String);
|
pub struct TicketTitle(String);
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn thirthieth() {
|
fn thirtieth() {
|
||||||
assert_eq!(fibonacci(30), 832040);
|
assert_eq!(fibonacci(30), 832040);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
// TODO: Define a function named `lowercase` that converts all characters in a string to lowercase,
|
// TODO: Define a function named `squared` that raises all `i32`s within a slice to the power of 2.
|
||||||
// modifying the input in place.
|
// The slice should be modified in place.
|
||||||
// Does it need to take a `&mut String`? Does a `&mut [str]` work? Why or why not?
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
@@ -8,29 +7,22 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn empty() {
|
fn empty() {
|
||||||
let mut s = String::from("");
|
let mut s = vec![];
|
||||||
lowercase(&mut s);
|
squared(&mut s);
|
||||||
assert_eq!(s, "");
|
assert_eq!(s, vec![]);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn one_char() {
|
fn one() {
|
||||||
let mut s = String::from("A");
|
let mut s = [2];
|
||||||
lowercase(&mut s);
|
squared(&mut s);
|
||||||
assert_eq!(s, "a");
|
assert_eq!(s, [4]);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn multiple_chars() {
|
fn multiple() {
|
||||||
let mut s = String::from("Hello, World!");
|
let mut s = vec![2, 4];
|
||||||
lowercase(&mut s);
|
squared(&mut s);
|
||||||
assert_eq!(s, "hello, world!");
|
assert_eq!(s, vec![4, 16]);
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn mut_slice() {
|
|
||||||
let mut s = "Hello, World!".to_string();
|
|
||||||
lowercase(s.as_mut_str());
|
|
||||||
assert_eq!(s, "hello, world!");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -99,5 +99,11 @@ mod tests {
|
|||||||
|
|
||||||
let ticket = &store[id];
|
let ticket = &store[id];
|
||||||
assert_eq!(ticket.status, Status::InProgress);
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ pub enum Command {
|
|||||||
Insert(todo!()),
|
Insert(todo!()),
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start the system by spawning the server the thread.
|
// Start the system by spawning the server thread.
|
||||||
// It returns a `Sender` instance which can then be used
|
// It returns a `Sender` instance which can then be used
|
||||||
// by one or more clients to interact with the server.
|
// by one or more clients to interact with the server.
|
||||||
pub fn launch() -> Sender<Command> {
|
pub fn launch() -> Sender<Command> {
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ enum Command {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn server(receiver: Receiver<Command>) {
|
fn server(receiver: Receiver<Command>) {
|
||||||
let mut store = TicketStore::new();
|
let mut store = TicketStore::new();
|
||||||
loop {
|
loop {
|
||||||
match receiver.recv() {
|
match receiver.recv() {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// TODO: Implement the `fixed_reply` function. It should accept two `TcpListener` instances,
|
// TODO: Implement the `fixed_reply` function. It should accept two `TcpListener` instances,
|
||||||
// accept connections on both of them concurrently, and always reply clients by sending
|
// accept connections on both of them concurrently, and always reply to clients by sending
|
||||||
// the `Display` representation of the `reply` argument as a response.
|
// the `Display` representation of the `reply` argument as a response.
|
||||||
use std::fmt::Display;
|
use std::fmt::Display;
|
||||||
use tokio::io::AsyncWriteExt;
|
use tokio::io::AsyncWriteExt;
|
||||||
|
|||||||
12
helpers/json2redirects.sh
Executable file
12
helpers/json2redirects.sh
Executable file
@@ -0,0 +1,12 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Ensure the JSON file is provided as an argument
|
||||||
|
if [ "$#" -ne 1 ]; then
|
||||||
|
echo "Usage: $0 <input_json_file>"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
input_file=$1
|
||||||
|
|
||||||
|
# Use jq to parse the JSON and format the output
|
||||||
|
jq -r 'to_entries[] | "/" + .value + " " + .key' "$input_file"
|
||||||
@@ -3,7 +3,6 @@ use mdbook::book::Book;
|
|||||||
use mdbook::preprocess::{Preprocessor, PreprocessorContext};
|
use mdbook::preprocess::{Preprocessor, PreprocessorContext};
|
||||||
use mdbook::BookItem;
|
use mdbook::BookItem;
|
||||||
|
|
||||||
/// A no-op preprocessor.
|
|
||||||
pub struct ExerciseLinker;
|
pub struct ExerciseLinker;
|
||||||
|
|
||||||
impl ExerciseLinker {
|
impl ExerciseLinker {
|
||||||
@@ -33,7 +32,7 @@ impl Preprocessor for ExerciseLinker {
|
|||||||
|
|
||||||
book.sections
|
book.sections
|
||||||
.iter_mut()
|
.iter_mut()
|
||||||
.for_each(|i| process_book_item(i, &root_url));
|
.for_each(|i| process_book_item(i, &ctx.renderer, &root_url));
|
||||||
Ok(book)
|
Ok(book)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -42,11 +41,11 @@ impl Preprocessor for ExerciseLinker {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn process_book_item(item: &mut BookItem, root_url: &str) {
|
fn process_book_item(item: &mut BookItem, renderer: &str, root_url: &str) {
|
||||||
match item {
|
match item {
|
||||||
BookItem::Chapter(chapter) => {
|
BookItem::Chapter(chapter) => {
|
||||||
chapter.sub_items.iter_mut().for_each(|item| {
|
chapter.sub_items.iter_mut().for_each(|item| {
|
||||||
process_book_item(item, root_url);
|
process_book_item(item, renderer, root_url);
|
||||||
});
|
});
|
||||||
|
|
||||||
let Some(source_path) = &chapter.source_path else {
|
let Some(source_path) = &chapter.source_path else {
|
||||||
@@ -61,10 +60,14 @@ fn process_book_item(item: &mut BookItem, root_url: &str) {
|
|||||||
|
|
||||||
let exercise_path = source_path.strip_suffix(".md").unwrap();
|
let exercise_path = source_path.strip_suffix(".md").unwrap();
|
||||||
let link_section = format!(
|
let link_section = format!(
|
||||||
"\n## Exercise\n\nThe exercise for this section is located in [`{exercise_path}`]({})",
|
"\n## Exercise\n\nThe exercise for this section is located in [`{exercise_path}`]({})\n",
|
||||||
format!("{}/{}", root_url, exercise_path)
|
format!("{}/{}", root_url, exercise_path)
|
||||||
);
|
);
|
||||||
chapter.content.push_str(&link_section);
|
chapter.content.push_str(&link_section);
|
||||||
|
|
||||||
|
if renderer == "pandoc" {
|
||||||
|
chapter.content.push_str("`\\newpage`{=latex}\n");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
BookItem::Separator => {}
|
BookItem::Separator => {}
|
||||||
BookItem::PartTitle(_) => {}
|
BookItem::PartTitle(_) => {}
|
||||||
|
|||||||
15
helpers/mdbook-link-shortener/Cargo.toml
Normal file
15
helpers/mdbook-link-shortener/Cargo.toml
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
[package]
|
||||||
|
name = "mdbook-link-shortener"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
anyhow = "1.0.86"
|
||||||
|
bimap = { version = "0.6.3", features = ["serde"] }
|
||||||
|
clap = { version = "4.5.4", features = ["derive"] }
|
||||||
|
itertools = "0.13.0"
|
||||||
|
mdbook = "0.4.40"
|
||||||
|
pulldown-cmark = "0.11.0"
|
||||||
|
pulldown-cmark-to-cmark = "15"
|
||||||
|
semver = "1.0.23"
|
||||||
|
serde_json = "1.0.117"
|
||||||
224
helpers/mdbook-link-shortener/src/lib.rs
Normal file
224
helpers/mdbook-link-shortener/src/lib.rs
Normal file
@@ -0,0 +1,224 @@
|
|||||||
|
use anyhow::{Context, Error};
|
||||||
|
use bimap::BiHashMap;
|
||||||
|
use itertools::Itertools;
|
||||||
|
use mdbook::book::{Book, Chapter};
|
||||||
|
use mdbook::preprocess::{Preprocessor, PreprocessorContext};
|
||||||
|
use mdbook::BookItem;
|
||||||
|
use std::collections::{BTreeMap, BTreeSet};
|
||||||
|
use std::fs::File;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
pub struct LinkShortener;
|
||||||
|
|
||||||
|
struct AliasGenerator {
|
||||||
|
cursors: [usize; 3],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AliasGenerator {
|
||||||
|
const ALPHABET: &'static [u8] = b"f2z4x6v8bnm3q5w7e9rtyuplkshgjdca";
|
||||||
|
|
||||||
|
fn new() -> AliasGenerator {
|
||||||
|
AliasGenerator { cursors: [0, 0, 0] }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Generate a 4 alphanumeric long alias, starting from "aaaa" and incrementing by one each time
|
||||||
|
/// until "9999", using only lowercase letters and numbers.
|
||||||
|
/// We skip ambiguous characters like "0", "o", "1", "l".
|
||||||
|
fn next(&mut self) -> String {
|
||||||
|
let mut alias = String::with_capacity(4);
|
||||||
|
for cursor in &mut self.cursors {
|
||||||
|
alias.push(Self::ALPHABET[*cursor] as char);
|
||||||
|
}
|
||||||
|
|
||||||
|
for cursor in self.cursors.iter_mut().rev() {
|
||||||
|
if *cursor == Self::ALPHABET.len() - 1 {
|
||||||
|
*cursor = 0;
|
||||||
|
} else {
|
||||||
|
*cursor += 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
alias
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Generate a unique alias that is not already used by the `link2alias` map.
|
||||||
|
fn next_until_unique(&mut self, link2alias: &BiHashMap<String, String>) -> String {
|
||||||
|
let mut alias = self.next();
|
||||||
|
while link2alias.contains_right(&alias) {
|
||||||
|
alias = self.next();
|
||||||
|
}
|
||||||
|
alias
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LinkShortener {
|
||||||
|
pub fn new() -> LinkShortener {
|
||||||
|
LinkShortener
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Preprocessor for LinkShortener {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"link-shortener"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(&self, ctx: &PreprocessorContext, mut book: Book) -> Result<Book, Error> {
|
||||||
|
let config = ctx
|
||||||
|
.config
|
||||||
|
.get_preprocessor(self.name())
|
||||||
|
.context("Failed to get preprocessor configuration")?;
|
||||||
|
let root_url = {
|
||||||
|
let root_url = config.get("base_url").context("Failed to get `base_url`")?;
|
||||||
|
root_url
|
||||||
|
.as_str()
|
||||||
|
.context("`base_url` is not a string")?
|
||||||
|
.to_owned()
|
||||||
|
};
|
||||||
|
let mapping = {
|
||||||
|
let mapping = config.get("mapping").context("Failed to get `mapping`")?;
|
||||||
|
let mapping = mapping
|
||||||
|
.as_str()
|
||||||
|
.context("`mapping` is not a string")?
|
||||||
|
.to_owned();
|
||||||
|
PathBuf::from_str(&mapping).context("Failed to parse `mapping` as a path")?
|
||||||
|
};
|
||||||
|
let mut link2alias = {
|
||||||
|
match File::open(&mapping) {
|
||||||
|
Ok(file) => {
|
||||||
|
serde_json::from_reader(file).context("Failed to parse existing mapping")?
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
if e.kind() == std::io::ErrorKind::NotFound {
|
||||||
|
BiHashMap::new()
|
||||||
|
} else {
|
||||||
|
return Err(e).context("Failed to open existing mapping");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let verify = config
|
||||||
|
.get("verify")
|
||||||
|
.context("Failed to get `verify`")?
|
||||||
|
.as_bool()
|
||||||
|
.context("`verify` is not a boolean")?;
|
||||||
|
// Env var overrides config
|
||||||
|
let verify = std::env::var("LINK_SHORTENER_VERIFY")
|
||||||
|
.map(|v| v == "true")
|
||||||
|
.unwrap_or(verify);
|
||||||
|
|
||||||
|
let mut alias_gen = AliasGenerator::new();
|
||||||
|
|
||||||
|
book.sections.iter_mut().for_each(|i| {
|
||||||
|
if let BookItem::Chapter(c) = i {
|
||||||
|
c.content = replace_anchors(c, &root_url, &mut alias_gen, &mut link2alias, verify)
|
||||||
|
.expect("Error converting links for chapter");
|
||||||
|
for i in c.sub_items.iter_mut() {
|
||||||
|
if let BookItem::Chapter(sub_chapter) = i {
|
||||||
|
sub_chapter.content = replace_anchors(
|
||||||
|
sub_chapter,
|
||||||
|
&root_url,
|
||||||
|
&mut alias_gen,
|
||||||
|
&mut link2alias,
|
||||||
|
verify,
|
||||||
|
)
|
||||||
|
.expect("Error converting links for subchapter");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if !verify {
|
||||||
|
std::fs::create_dir_all(mapping.parent().expect("Mapping file path has no parent"))?;
|
||||||
|
let mut file = File::create(&mapping).context("Failed to upsert mapping file")?;
|
||||||
|
let ordered = link2alias.iter().collect::<BTreeMap<_, _>>();
|
||||||
|
serde_json::to_writer_pretty(&mut file, &ordered)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(book)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn supports_renderer(&self, _renderer: &str) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn replace_anchors(
|
||||||
|
chapter: &mut Chapter,
|
||||||
|
root_url: &str,
|
||||||
|
alias_gen: &mut AliasGenerator,
|
||||||
|
link2alias: &mut BiHashMap<String, String>,
|
||||||
|
verify: bool,
|
||||||
|
) -> Result<String, anyhow::Error> {
|
||||||
|
use pulldown_cmark::{CowStr, Event, LinkType, Options, Parser, Tag};
|
||||||
|
use pulldown_cmark_to_cmark::cmark;
|
||||||
|
|
||||||
|
let mut buf = String::with_capacity(chapter.content.len());
|
||||||
|
|
||||||
|
let mut unshortened_links = BTreeSet::new();
|
||||||
|
let events = Parser::new_ext(&chapter.content, Options::all())
|
||||||
|
.map(|e| {
|
||||||
|
let Event::Start(Tag::Link {
|
||||||
|
link_type,
|
||||||
|
dest_url,
|
||||||
|
title,
|
||||||
|
id,
|
||||||
|
}) = &e
|
||||||
|
else {
|
||||||
|
return e;
|
||||||
|
};
|
||||||
|
|
||||||
|
match link_type {
|
||||||
|
LinkType::Autolink
|
||||||
|
| LinkType::Shortcut
|
||||||
|
| LinkType::Inline
|
||||||
|
| LinkType::Reference
|
||||||
|
| LinkType::Collapsed => {
|
||||||
|
if dest_url.starts_with("http") {
|
||||||
|
let alias = if let Some(alias) = link2alias.get_by_left(dest_url.as_ref()) {
|
||||||
|
alias.to_owned()
|
||||||
|
} else {
|
||||||
|
if verify {
|
||||||
|
unshortened_links.insert(dest_url.to_string());
|
||||||
|
return e;
|
||||||
|
}
|
||||||
|
let alias = alias_gen.next_until_unique(&link2alias);
|
||||||
|
alias
|
||||||
|
};
|
||||||
|
link2alias.insert(dest_url.to_string(), alias.clone());
|
||||||
|
|
||||||
|
Event::Start(Tag::Link {
|
||||||
|
link_type: link_type.to_owned(),
|
||||||
|
dest_url: CowStr::from(format!(
|
||||||
|
"{root_url}/{alias}",
|
||||||
|
root_url = root_url,
|
||||||
|
alias = alias
|
||||||
|
)),
|
||||||
|
title: title.clone(),
|
||||||
|
id: id.clone(),
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
e
|
||||||
|
}
|
||||||
|
}
|
||||||
|
LinkType::Email
|
||||||
|
| LinkType::ReferenceUnknown
|
||||||
|
| LinkType::CollapsedUnknown
|
||||||
|
| LinkType::ShortcutUnknown => e,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect_vec();
|
||||||
|
|
||||||
|
if verify && !unshortened_links.is_empty() {
|
||||||
|
let unshortened_links = unshortened_links.iter().join(", ");
|
||||||
|
return Err(anyhow::anyhow!(
|
||||||
|
"The following links are not shortened: {unshortened_links}\nRun again with `LINK_SHORTENER_VERIFY=false` to update the mapping \
|
||||||
|
with the shortened links."
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
cmark(events.into_iter(), &mut buf)
|
||||||
|
.map(|_| buf)
|
||||||
|
.map_err(|err| anyhow::anyhow!("Markdown serialization failed: {err}"))
|
||||||
|
}
|
||||||
66
helpers/mdbook-link-shortener/src/main.rs
Normal file
66
helpers/mdbook-link-shortener/src/main.rs
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
use clap::Parser;
|
||||||
|
use mdbook::errors::Error;
|
||||||
|
use mdbook::preprocess::{CmdPreprocessor, Preprocessor};
|
||||||
|
use mdbook_link_shortener::LinkShortener;
|
||||||
|
use semver::{Version, VersionReq};
|
||||||
|
use std::io;
|
||||||
|
use std::process;
|
||||||
|
|
||||||
|
#[derive(clap::Parser, Debug)]
|
||||||
|
#[command(version, about)]
|
||||||
|
pub struct Cli {
|
||||||
|
#[command(subcommand)]
|
||||||
|
sub: Option<SubCommand>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(clap::Parser, Debug)]
|
||||||
|
pub enum SubCommand {
|
||||||
|
#[clap(name = "supports")]
|
||||||
|
Supports(Supports),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(clap::Parser, Debug)]
|
||||||
|
pub struct Supports {
|
||||||
|
#[arg(long)]
|
||||||
|
renderer: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() -> Result<(), anyhow::Error> {
|
||||||
|
let cli = Cli::parse();
|
||||||
|
let preprocessor = LinkShortener::new();
|
||||||
|
|
||||||
|
if let Some(SubCommand::Supports(Supports { renderer })) = cli.sub {
|
||||||
|
let code = if preprocessor.supports_renderer(&renderer) {
|
||||||
|
0
|
||||||
|
} else {
|
||||||
|
1
|
||||||
|
};
|
||||||
|
process::exit(code);
|
||||||
|
}
|
||||||
|
|
||||||
|
handle_preprocessing(&preprocessor)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_preprocessing(pre: &dyn Preprocessor) -> Result<(), Error> {
|
||||||
|
let (ctx, book) = CmdPreprocessor::parse_input(io::stdin())?;
|
||||||
|
|
||||||
|
let book_version = Version::parse(&ctx.mdbook_version)?;
|
||||||
|
let version_req = VersionReq::parse(mdbook::MDBOOK_VERSION)?;
|
||||||
|
|
||||||
|
if !version_req.matches(&book_version) {
|
||||||
|
eprintln!(
|
||||||
|
"Warning: The {} plugin was built against version {} of mdbook, \
|
||||||
|
but we're being called from version {}",
|
||||||
|
pre.name(),
|
||||||
|
mdbook::MDBOOK_VERSION,
|
||||||
|
ctx.mdbook_version
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let processed_book = pre.run(&ctx, book)?;
|
||||||
|
serde_json::to_writer(io::stdout(), &processed_book)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
188
site/_redirects
Normal file
188
site/_redirects
Normal file
@@ -0,0 +1,188 @@
|
|||||||
|
/f2u https://blog.acolyer.org/2019/05/28/cheri-abi/
|
||||||
|
/f4q https://crates.io
|
||||||
|
/f2n https://crates.io/crates/cargo-modules
|
||||||
|
/ffr https://doc.rust-lang.org/book/ch03-02-data-types.html#integer-types
|
||||||
|
/f6t https://doc.rust-lang.org/book/title-page.html
|
||||||
|
/f4m https://doc.rust-lang.org/cargo/reference/cargo-targets.html#cargo-targets
|
||||||
|
/ffc https://doc.rust-lang.org/cargo/reference/profiles.html
|
||||||
|
/f45 https://doc.rust-lang.org/cargo/reference/specifying-dependencies.html
|
||||||
|
/f6u https://doc.rust-lang.org/nomicon/
|
||||||
|
/f2z https://doc.rust-lang.org/reference/expressions/operator-expr.html#numeric-cast
|
||||||
|
/fzf https://doc.rust-lang.org/reference/items/implementations.html#trait-implementation-coherence
|
||||||
|
/f4c https://doc.rust-lang.org/reference/lifetime-elision.html
|
||||||
|
/fxy https://doc.rust-lang.org/std/cell/struct.UnsafeCell.html
|
||||||
|
/fzm https://doc.rust-lang.org/std/cmp/index.html
|
||||||
|
/fzz https://doc.rust-lang.org/std/cmp/trait.PartialEq.html
|
||||||
|
/fzb https://doc.rust-lang.org/std/cmp/trait.PartialOrd.html
|
||||||
|
/fzp https://doc.rust-lang.org/std/convert/trait.From.html#implementors
|
||||||
|
/fzl https://doc.rust-lang.org/std/convert/trait.Into.html#implementors
|
||||||
|
/f4s https://doc.rust-lang.org/std/iter/trait.FusedIterator.html
|
||||||
|
/fxf https://doc.rust-lang.org/std/iter/trait.Iterator.html
|
||||||
|
/ffj https://doc.rust-lang.org/std/keyword.for.html
|
||||||
|
/ffh https://doc.rust-lang.org/std/keyword.while.html
|
||||||
|
/ffl https://doc.rust-lang.org/std/macro.panic.html
|
||||||
|
/f27 https://doc.rust-lang.org/std/mem/fn.size_of.html
|
||||||
|
/fzn https://doc.rust-lang.org/std/ops/index.html
|
||||||
|
/fz4 https://doc.rust-lang.org/std/ops/trait.Add.html
|
||||||
|
/fzt https://doc.rust-lang.org/std/ops/trait.Deref.html#deref-coercion
|
||||||
|
/fzv https://doc.rust-lang.org/std/ops/trait.Div.html
|
||||||
|
/fz6 https://doc.rust-lang.org/std/ops/trait.Mul.html
|
||||||
|
/fz8 https://doc.rust-lang.org/std/ops/trait.Rem.html
|
||||||
|
/fzx https://doc.rust-lang.org/std/ops/trait.Sub.html
|
||||||
|
/f2c https://doc.rust-lang.org/std/prelude/index.html
|
||||||
|
/ffe https://doc.rust-lang.org/std/primitive.i32.html#associatedconstant.MAX
|
||||||
|
/ff7 https://doc.rust-lang.org/std/primitive.i32.html#associatedconstant.MIN
|
||||||
|
/ffw https://doc.rust-lang.org/std/primitive.u32.html#associatedconstant.MAX
|
||||||
|
/f4d https://doc.rust-lang.org/std/slice/struct.Iter.html
|
||||||
|
/f26 https://doc.rust-lang.org/std/string/struct.String.html
|
||||||
|
/fxh https://doc.rust-lang.org/std/sync/atomic/index.html
|
||||||
|
/f4j https://doc.rust-lang.org/std/vec/struct.Vec.html#method.iter
|
||||||
|
/f2y https://docs.rs/dhat/latest/dhat/
|
||||||
|
/fx2 https://docs.rs/itertools/
|
||||||
|
/f4n https://docs.rs/thiserror/latest/thiserror/
|
||||||
|
/f65 https://docs.rs/tokio-stream/latest/tokio_stream/
|
||||||
|
/f6m https://docs.rs/tokio-stream/latest/tokio_stream/trait.StreamExt.html#method.merge
|
||||||
|
/f63 https://docs.rs/tokio-util/latest/tokio_util/sync/struct.CancellationToken.html
|
||||||
|
/f6z https://docs.rs/tokio/latest/tokio/task/struct.JoinError.html
|
||||||
|
/f6k https://docs.rust-embedded.org/book/
|
||||||
|
/f2h https://en.wikipedia.org/wiki/Dangling_pointer
|
||||||
|
/fx7 https://en.wikipedia.org/wiki/Data_segment
|
||||||
|
/f2r https://en.wikipedia.org/wiki/Memory_address
|
||||||
|
/f2e https://en.wikipedia.org/wiki/Stack_overflow
|
||||||
|
/ff9 https://en.wikipedia.org/wiki/Two%27s_complement
|
||||||
|
/f2v https://en.wikipedia.org/wiki/UTF-8
|
||||||
|
/f6r https://exercism.io
|
||||||
|
/ffb https://github.com/LukeMathWalker/cargo-chef
|
||||||
|
/ffm https://github.com/LukeMathWalker/wiremock-rs
|
||||||
|
/fzq https://github.com/dtolnay/cargo-expand
|
||||||
|
/fzw https://github.com/dtolnay/proc-macro-workshop
|
||||||
|
/ff6 https://github.com/mainmatter/100-exercises-to-learn-rust
|
||||||
|
/ff3 https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/01_intro/00_welcome
|
||||||
|
/ffq https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/01_intro/01_syntax
|
||||||
|
/ff5 https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/02_basic_calculator/00_intro
|
||||||
|
/fft https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/02_basic_calculator/01_integers
|
||||||
|
/ffy https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/02_basic_calculator/02_variables
|
||||||
|
/ffu https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/02_basic_calculator/03_if_else
|
||||||
|
/ffk https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/02_basic_calculator/04_panics
|
||||||
|
/ffs https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/02_basic_calculator/05_factorial
|
||||||
|
/ffg https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/02_basic_calculator/06_while
|
||||||
|
/ffd https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/02_basic_calculator/07_for
|
||||||
|
/f2f https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/02_basic_calculator/08_overflow
|
||||||
|
/f22 https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/02_basic_calculator/09_saturating
|
||||||
|
/f24 https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/02_basic_calculator/10_as_casting
|
||||||
|
/f2x https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/03_ticket_v1/00_intro
|
||||||
|
/f28 https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/03_ticket_v1/01_struct
|
||||||
|
/f2b https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/03_ticket_v1/02_validation
|
||||||
|
/f2m https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/03_ticket_v1/03_modules
|
||||||
|
/f23 https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/03_ticket_v1/04_visibility
|
||||||
|
/f2q https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/03_ticket_v1/05_encapsulation
|
||||||
|
/f25 https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/03_ticket_v1/06_ownership
|
||||||
|
/f2w https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/03_ticket_v1/07_setters
|
||||||
|
/f29 https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/03_ticket_v1/08_stack
|
||||||
|
/f2p https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/03_ticket_v1/09_heap
|
||||||
|
/f2l https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/03_ticket_v1/10_references_in_memory
|
||||||
|
/f2g https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/03_ticket_v1/11_destructor
|
||||||
|
/f2j https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/03_ticket_v1/12_outro
|
||||||
|
/f2d https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/04_traits/00_intro
|
||||||
|
/f2a https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/04_traits/01_trait
|
||||||
|
/fz2 https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/04_traits/02_orphan_rule
|
||||||
|
/fz3 https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/04_traits/03_operator_overloading
|
||||||
|
/fz7 https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/04_traits/04_derive
|
||||||
|
/fz9 https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/04_traits/05_trait_bounds
|
||||||
|
/fzr https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/04_traits/06_str_slice
|
||||||
|
/fzy https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/04_traits/07_deref
|
||||||
|
/fzu https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/04_traits/08_sized
|
||||||
|
/fzk https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/04_traits/09_from
|
||||||
|
/fzs https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/04_traits/10_assoc_vs_generic
|
||||||
|
/fzh https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/04_traits/11_clone
|
||||||
|
/fzg https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/04_traits/12_copy
|
||||||
|
/fzj https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/04_traits/13_drop
|
||||||
|
/fzc https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/04_traits/14_outro
|
||||||
|
/fza https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/05_ticket_v2/00_intro
|
||||||
|
/f4f https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/05_ticket_v2/01_enum
|
||||||
|
/f42 https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/05_ticket_v2/02_match
|
||||||
|
/f4z https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/05_ticket_v2/03_variants_with_data
|
||||||
|
/f44 https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/05_ticket_v2/04_if_let
|
||||||
|
/f4x https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/05_ticket_v2/05_nullability
|
||||||
|
/f46 https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/05_ticket_v2/06_fallibility
|
||||||
|
/f4v https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/05_ticket_v2/07_unwrap
|
||||||
|
/f48 https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/05_ticket_v2/08_error_enums
|
||||||
|
/f4b https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/05_ticket_v2/09_error_trait
|
||||||
|
/f43 https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/05_ticket_v2/10_packages
|
||||||
|
/f4w https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/05_ticket_v2/11_dependencies
|
||||||
|
/f47 https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/05_ticket_v2/12_thiserror
|
||||||
|
/f4e https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/05_ticket_v2/13_try_from
|
||||||
|
/f49 https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/05_ticket_v2/14_source
|
||||||
|
/f4y https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/05_ticket_v2/15_outro
|
||||||
|
/f4u https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/06_ticket_management/00_intro
|
||||||
|
/f4p https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/06_ticket_management/01_arrays
|
||||||
|
/f4l https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/06_ticket_management/02_vec
|
||||||
|
/f4k https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/06_ticket_management/03_resizing
|
||||||
|
/f4h https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/06_ticket_management/04_iterators
|
||||||
|
/f4g https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/06_ticket_management/05_iter
|
||||||
|
/f4a https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/06_ticket_management/06_lifetimes
|
||||||
|
/fxz https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/06_ticket_management/07_combinators
|
||||||
|
/fx4 https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/06_ticket_management/08_impl_trait
|
||||||
|
/fxx https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/06_ticket_management/09_impl_trait_2
|
||||||
|
/fx6 https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/06_ticket_management/10_slices
|
||||||
|
/fxv https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/06_ticket_management/11_mutable_slices
|
||||||
|
/fx8 https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/06_ticket_management/12_two_states
|
||||||
|
/fxb https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/06_ticket_management/13_index
|
||||||
|
/fxn https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/06_ticket_management/14_index_mut
|
||||||
|
/fxm https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/06_ticket_management/15_hashmap
|
||||||
|
/fx3 https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/06_ticket_management/16_btreemap
|
||||||
|
/fxq https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/07_threads/00_intro
|
||||||
|
/fxw https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/07_threads/01_threads
|
||||||
|
/fxe https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/07_threads/02_static
|
||||||
|
/fx9 https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/07_threads/03_leak
|
||||||
|
/fxr https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/07_threads/04_scoped_threads
|
||||||
|
/fxt https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/07_threads/05_channels
|
||||||
|
/fxu https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/07_threads/06_interior_mutability
|
||||||
|
/fxp https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/07_threads/07_ack
|
||||||
|
/fxl https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/07_threads/08_client
|
||||||
|
/fxk https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/07_threads/09_bounded
|
||||||
|
/fxs https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/07_threads/10_patch
|
||||||
|
/fxj https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/07_threads/11_locks
|
||||||
|
/fxd https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/07_threads/12_rw_lock
|
||||||
|
/fxc https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/07_threads/13_without_channels
|
||||||
|
/fxa https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/07_threads/14_sync
|
||||||
|
/f6f https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/08_futures/00_intro
|
||||||
|
/f62 https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/08_futures/01_async_fn
|
||||||
|
/f64 https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/08_futures/02_spawn
|
||||||
|
/f6x https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/08_futures/03_runtime
|
||||||
|
/f66 https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/08_futures/04_future
|
||||||
|
/f68 https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/08_futures/05_blocking
|
||||||
|
/f6b https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/08_futures/06_async_aware_primitives
|
||||||
|
/f6q https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/08_futures/07_cancellation
|
||||||
|
/f6e https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/08_futures/08_outro
|
||||||
|
/ffz https://github.com/mainmatter/100-exercises-to-learn-rust/tree/solutions
|
||||||
|
/fzd https://github.com/mainmatter/rust-advanced-testing-workshop
|
||||||
|
/f69 https://github.com/rust-lang/rustlings
|
||||||
|
/ffa https://huonw.github.io/blog/2016/04/myths-and-legends-about-integer-overflow-in-rust/
|
||||||
|
/f4r https://lexi-lambda.github.io/blog/2019/11/05/parse-don-t-validate/
|
||||||
|
/ff2 https://mainmatter.com/contact/
|
||||||
|
/fff https://mainmatter.com/rust-consulting/
|
||||||
|
/fxg https://marabos.nl/atomics/
|
||||||
|
/f6p https://nostarch.com/rust-rustaceans
|
||||||
|
/f2k https://owasp.org/www-community/vulnerabilities/Doubly_freeing_memory
|
||||||
|
/f2s https://owasp.org/www-community/vulnerabilities/Using_freed_memory
|
||||||
|
/ffn https://pavex.dev
|
||||||
|
/ffp https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=36e5ddbe3b3f741dfa9f74c956622bac
|
||||||
|
/fx5 https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=afedf7062298ca8f5a248bc551062eaa
|
||||||
|
/ffx https://rust-exercises.com/100-exercises-to-learn-rust.pdf
|
||||||
|
/ff4 https://rust-exercises.com/100-exercises/
|
||||||
|
/f6s https://rust-exercises.com/advanced-testing/
|
||||||
|
/f6h https://rust-exercises.com/telemetry/
|
||||||
|
/fze https://rust-lang.github.io/api-guidelines/naming.html#casing-conforms-to-rfc-430-c-case
|
||||||
|
/f6w https://rust-lang.github.io/wg-async/vision/submitted_stories/status_quo/barbara_battles_buffered_streams.html
|
||||||
|
/f6v https://ryhl.io/blog/async-what-is-blocking/
|
||||||
|
/f6n https://tokio.rs/tokio/tutorial/select
|
||||||
|
/f2t https://valgrind.org/docs/manual/dh-manual.html
|
||||||
|
/fz5 https://veykril.github.io/tlborm/
|
||||||
|
/f67 https://without.boats/blog/the-scoped-task-trilemma/
|
||||||
|
/f6g https://www.amazon.com/dp/B0DJ14KQQG/
|
||||||
|
/ffv https://www.lpalmieri.com/
|
||||||
|
/f4t https://www.lpalmieri.com/posts/2020-12-11-zero-to-production-6-domain-modelling/
|
||||||
|
/f6y https://www.oreilly.com/library/view/programming-rust-2nd/9781492052586/
|
||||||
|
/f6l https://www.youtube.com/playlist?list=PLqbS7AVVErFirH9armw8yXlE6dacF-A6z
|
||||||
|
/ff8 https://zero2prod.com
|
||||||
Reference in New Issue
Block a user