├── .all-contributorsrc
├── .devcontainer
├── devcontainer.json
└── setup.sh
├── .editorconfig
├── .github
├── classroom
│ └── checkresult.js
├── result
│ └── check_result.json
└── workflows
│ └── rust.yml
├── .gitignore
├── .gitpod.yml
├── .markdownlint.yml
├── .vscode
└── extensions.json
├── AUTHORS.md
├── CHANGELOG.md
├── CONTRIBUTING.md
├── Cargo.lock
├── Cargo.toml
├── LICENSE
├── README.md
├── exercises
├── README.md
├── generics
│ ├── README.md
│ ├── generics1.rs
│ └── generics2.rs
├── hashmaps
│ ├── README.md
│ ├── hashmaps1.rs
│ ├── hashmaps2.rs
│ └── hashmaps3.rs
├── iterators
│ ├── README.md
│ ├── iterators1.rs
│ └── iterators2.rs
├── lifetimes
│ ├── README.md
│ ├── lifetimes1.rs
│ ├── lifetimes2.rs
│ └── lifetimes3.rs
├── macros
│ ├── README.md
│ ├── macros1.rs
│ └── macros2.rs
├── modules
│ ├── README.md
│ └── modules2.rs
├── move_semantics
│ ├── README.md
│ ├── move_semantics4.rs
│ ├── move_semantics5.rs
│ └── move_semantics6.rs
├── options
│ ├── README.md
│ └── options1.rs
├── quiz2.rs
├── quiz3.rs
├── smart_pointers
│ ├── README.md
│ ├── arc1.rs
│ ├── box1.rs
│ ├── cow1.rs
│ └── rc1.rs
├── strings
│ ├── README.md
│ ├── strings3.rs
│ └── strings4.rs
├── structs
│ ├── README.md
│ └── structs1.rs
├── traits
│ ├── README.md
│ ├── traits3.rs
│ └── traits4.rs
└── vecs
│ ├── README.md
│ ├── vecs1.rs
│ └── vecs2.rs
├── flake.lock
├── flake.nix
├── info.toml
├── install.ps1
├── install.sh
├── oranda.json
├── shell.nix
├── src
├── exercise.rs
├── main.rs
├── project.rs
├── run.rs
├── ui.rs
└── verify.rs
└── tests
├── cicv.rs
├── fixture
├── failure
│ ├── compFailure.rs
│ ├── compNoExercise.rs
│ ├── info.toml
│ ├── testFailure.rs
│ └── testNotPassed.rs
├── state
│ ├── finished_exercise.rs
│ ├── info.toml
│ ├── pending_exercise.rs
│ └── pending_test_exercise.rs
└── success
│ ├── compSuccess.rs
│ ├── info.toml
│ └── testSuccess.rs
└── integration_tests.rs
/.devcontainer/devcontainer.json:
--------------------------------------------------------------------------------
1 | {
2 | "image": "mcr.microsoft.com/devcontainers/universal:2-linux",
3 | "waitFor": "onCreateCommand",
4 | "onCreateCommand": ".devcontainer/setup.sh",
5 | "updateContentCommand": "cargo build",
6 | "postCreateCommand": "",
7 | "postAttachCommand": {
8 | "server": "rustlings watch"
9 | },
10 | "customizations": {
11 | "vscode": {
12 | "extensions": [
13 | "rust-lang.rust-analyzer"
14 | ]
15 | }
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/.devcontainer/setup.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | curl https://sh.rustup.rs -sSf | sh -s -- -y
3 |
4 | # Update current shell environment variables after install to find rustup
5 | . "$HOME/.cargo/env"
6 | rustup install stable
7 | cargo install --force --path .
8 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*.rs]
4 | end_of_line = lf
5 | insert_final_newline = true
6 | indent_style = space
7 | indent_size = 4
8 |
--------------------------------------------------------------------------------
/.github/classroom/checkresult.js:
--------------------------------------------------------------------------------
1 | // TODO 判断文件是否存在
2 |
3 | function judge(outputFile) {
4 | try {
5 | let jsonResult = JSON.parse(outputFile);
6 | let points = {};
7 | jsonResult.exercises.forEach(({ name, result }) => {
8 | if (result) {
9 | points[name] = [1,1]
10 | } else {
11 | points[name] = [0,1]
12 | }
13 | })
14 | return points;
15 | } catch(e) {
16 | return {};
17 | }
18 | }
19 |
20 | module.exports.judge = judge;
--------------------------------------------------------------------------------
/.github/result/check_result.json:
--------------------------------------------------------------------------------
1 | {
2 | "exercises": [
3 | {
4 | "name": "intro2",
5 | "result": false
6 | },
7 | {
8 | "name": "move_semantics1",
9 | "result": false
10 | },
11 | {
12 | "name": "functions3",
13 | "result": false
14 | },
15 | {
16 | "name": "primitive_types2",
17 | "result": false
18 | },
19 | {
20 | "name": "options2",
21 | "result": false
22 | },
23 | {
24 | "name": "hashmaps1",
25 | "result": false
26 | },
27 | {
28 | "name": "strings2",
29 | "result": false
30 | },
31 | {
32 | "name": "structs2",
33 | "result": false
34 | },
35 | {
36 | "name": "variables5",
37 | "result": false
38 | },
39 | {
40 | "name": "generics2",
41 | "result": false
42 | },
43 | {
44 | "name": "variables2",
45 | "result": false
46 | },
47 | {
48 | "name": "variables1",
49 | "result": false
50 | },
51 | {
52 | "name": "functions4",
53 | "result": false
54 | },
55 | {
56 | "name": "move_semantics2",
57 | "result": false
58 | },
59 | {
60 | "name": "primitive_types3",
61 | "result": false
62 | },
63 | {
64 | "name": "options3",
65 | "result": false
66 | },
67 | {
68 | "name": "variables6",
69 | "result": false
70 | },
71 | {
72 | "name": "strings3",
73 | "result": false
74 | },
75 | {
76 | "name": "traits1",
77 | "result": false
78 | },
79 | {
80 | "name": "variables3",
81 | "result": false
82 | },
83 | {
84 | "name": "traits4",
85 | "result": false
86 | },
87 | {
88 | "name": "functions5",
89 | "result": false
90 | },
91 | {
92 | "name": "structs3",
93 | "result": false
94 | },
95 | {
96 | "name": "strings4",
97 | "result": false
98 | },
99 | {
100 | "name": "errors1",
101 | "result": false
102 | },
103 | {
104 | "name": "move_semantics3",
105 | "result": false
106 | },
107 | {
108 | "name": "primitive_types4",
109 | "result": false
110 | },
111 | {
112 | "name": "functions1",
113 | "result": false
114 | },
115 | {
116 | "name": "traits2",
117 | "result": false
118 | },
119 | {
120 | "name": "variables4",
121 | "result": false
122 | },
123 | {
124 | "name": "if1",
125 | "result": false
126 | },
127 | {
128 | "name": "traits5",
129 | "result": false
130 | },
131 | {
132 | "name": "modules1",
133 | "result": false
134 | },
135 | {
136 | "name": "errors2",
137 | "result": false
138 | },
139 | {
140 | "name": "enums1",
141 | "result": false
142 | },
143 | {
144 | "name": "move_semantics4",
145 | "result": false
146 | },
147 | {
148 | "name": "primitive_types5",
149 | "result": false
150 | },
151 | {
152 | "name": "lifetimes2",
153 | "result": false
154 | },
155 | {
156 | "name": "traits3",
157 | "result": false
158 | },
159 | {
160 | "name": "functions2",
161 | "result": false
162 | },
163 | {
164 | "name": "if2",
165 | "result": false
166 | },
167 | {
168 | "name": "modules2",
169 | "result": false
170 | },
171 | {
172 | "name": "errors3",
173 | "result": false
174 | },
175 | {
176 | "name": "move_semantics5",
177 | "result": false
178 | },
179 | {
180 | "name": "enums2",
181 | "result": false
182 | },
183 | {
184 | "name": "lifetimes3",
185 | "result": false
186 | },
187 | {
188 | "name": "primitive_types6",
189 | "result": false
190 | },
191 | {
192 | "name": "tests2",
193 | "result": false
194 | },
195 | {
196 | "name": "iterators1",
197 | "result": false
198 | },
199 | {
200 | "name": "modules3",
201 | "result": false
202 | },
203 | {
204 | "name": "if3",
205 | "result": false
206 | },
207 | {
208 | "name": "iterators4",
209 | "result": false
210 | },
211 | {
212 | "name": "tests1",
213 | "result": false
214 | },
215 | {
216 | "name": "enums3",
217 | "result": false
218 | },
219 | {
220 | "name": "move_semantics6",
221 | "result": false
222 | },
223 | {
224 | "name": "vecs1",
225 | "result": false
226 | },
227 | {
228 | "name": "tests3",
229 | "result": false
230 | },
231 | {
232 | "name": "iterators2",
233 | "result": false
234 | },
235 | {
236 | "name": "quiz1",
237 | "result": false
238 | },
239 | {
240 | "name": "tests4",
241 | "result": false
242 | },
243 | {
244 | "name": "vecs2",
245 | "result": false
246 | },
247 | {
248 | "name": "strings1",
249 | "result": false
250 | },
251 | {
252 | "name": "cow1",
253 | "result": false
254 | },
255 | {
256 | "name": "structs1",
257 | "result": false
258 | },
259 | {
260 | "name": "primitive_types1",
261 | "result": false
262 | },
263 | {
264 | "name": "macros1",
265 | "result": false
266 | },
267 | {
268 | "name": "macros3",
269 | "result": false
270 | },
271 | {
272 | "name": "threads2",
273 | "result": false
274 | },
275 | {
276 | "name": "errors4",
277 | "result": false
278 | },
279 | {
280 | "name": "macros4",
281 | "result": false
282 | },
283 | {
284 | "name": "errors5",
285 | "result": false
286 | },
287 | {
288 | "name": "macros2",
289 | "result": false
290 | },
291 | {
292 | "name": "threads3",
293 | "result": false
294 | },
295 | {
296 | "name": "using_as",
297 | "result": false
298 | },
299 | {
300 | "name": "from_into",
301 | "result": false
302 | },
303 | {
304 | "name": "from_str",
305 | "result": false
306 | },
307 | {
308 | "name": "try_from_into",
309 | "result": false
310 | },
311 | {
312 | "name": "as_ref_mut",
313 | "result": false
314 | },
315 | {
316 | "name": "tests5",
317 | "result": false
318 | },
319 | {
320 | "name": "tests6",
321 | "result": false
322 | },
323 | {
324 | "name": "tests7",
325 | "result": false
326 | },
327 | {
328 | "name": "tests8",
329 | "result": false
330 | },
331 | {
332 | "name": "hashmaps2",
333 | "result": false
334 | },
335 | {
336 | "name": "tests9",
337 | "result": false
338 | },
339 | {
340 | "name": "generics1",
341 | "result": false
342 | },
343 | {
344 | "name": "lifetimes1",
345 | "result": false
346 | },
347 | {
348 | "name": "quiz2",
349 | "result": false
350 | },
351 | {
352 | "name": "box1",
353 | "result": false
354 | },
355 | {
356 | "name": "options1",
357 | "result": false
358 | },
359 | {
360 | "name": "arc1",
361 | "result": false
362 | },
363 | {
364 | "name": "clippy1",
365 | "result": false
366 | },
367 | {
368 | "name": "clippy3",
369 | "result": false
370 | },
371 | {
372 | "name": "quiz3",
373 | "result": false
374 | },
375 | {
376 | "name": "clippy2",
377 | "result": false
378 | },
379 | {
380 | "name": "rc1",
381 | "result": false
382 | },
383 | {
384 | "name": "iterators5",
385 | "result": false
386 | },
387 | {
388 | "name": "iterators3",
389 | "result": false
390 | },
391 | {
392 | "name": "threads1",
393 | "result": false
394 | },
395 | {
396 | "name": "errors6",
397 | "result": false
398 | },
399 | {
400 | "name": "hashmaps3",
401 | "result": false
402 | },
403 | {
404 | "name": "algorithm1",
405 | "result": false
406 | },
407 | {
408 | "name": "algorithm2",
409 | "result": false
410 | },
411 | {
412 | "name": "algorithm3",
413 | "result": false
414 | },
415 | {
416 | "name": "algorithm4",
417 | "result": false
418 | },
419 | {
420 | "name": "algorithm5",
421 | "result": false
422 | },
423 | {
424 | "name": "algorithm6",
425 | "result": false
426 | },
427 | {
428 | "name": "algorithm7",
429 | "result": false
430 | },
431 | {
432 | "name": "algorithm8",
433 | "result": false
434 | },
435 | {
436 | "name": "algorithm9",
437 | "result": false
438 | },
439 | {
440 | "name": "algorithm10",
441 | "result": false
442 | }
443 | ],
444 | "user_name": null,
445 | "statistics": {
446 | "total_exercations": 100,
447 | "total_succeeds": 0,
448 | "total_failures": 100,
449 | "total_time": 3
450 | }
451 | }
--------------------------------------------------------------------------------
/.github/workflows/rust.yml:
--------------------------------------------------------------------------------
1 | name: GitHub Classroom Workflow
2 |
3 | on:
4 | push:
5 | branches: [ main ]
6 | paths-ignore:
7 | - 'README.md'
8 | pull_request:
9 | branches: [ main ]
10 | workflow_dispatch:
11 |
12 | env:
13 | CARGO_TERM_COLOR: always
14 | TZ: Asia/Shanghai # 设置时区
15 | API_URL: "aHR0cHM6Ly9hcGkub3BlbmNhbXAuY24vd2ViL2FwaS9jb3Vyc2VSYW5rL2NyZWF0ZUJ5VGhpcmRUb2tlbg=="
16 | TOKEN: "bXJua242eXp0NXFUYm9DR1ZEb25QOWIzU2lNQ0hw"
17 |
18 | jobs:
19 | build:
20 | name: Autograding
21 | runs-on: ubuntu-latest
22 | outputs:
23 | details: ${{ steps.autograding.outputs.details }}
24 | points: ${{ steps.autograding.outputs.points }}
25 | steps:
26 | - name: Decode API URL
27 | id: decode_api_url
28 | run: |
29 | echo "url=$(echo "$API_URL" | base64 --decode)" >> $GITHUB_ENV
30 | - name: Decode Token
31 | id: decode_token
32 | run: |
33 | echo "token=$(echo "$TOKEN" | base64 --decode)" >> $GITHUB_ENV
34 | - uses: actions/checkout@v3
35 | - name: Run tests
36 | run: cargo test --test cicv --verbose
37 | - uses: yfblock/os-autograding@master
38 | id: autograding
39 | with:
40 | outputFile: .github/result/check_result.json
41 | - name: Generate summary JSON
42 | run: |
43 | outfile=".github/result/check_result.json"
44 | summary_file=".github/result/summary.json"
45 |
46 | # 提取需要的值
47 | total_exercations=$(jq '.statistics.total_exercations' $outfile)
48 | total_succeeds=$(jq '.statistics.total_succeeds' $outfile)
49 | github_user="${{ github.actor }}"
50 |
51 | # 生成新的 JSON 内容
52 | new_json=$(jq -n \
53 | --arg channel "github" \
54 | --argjson courseId 1528 \
55 | --arg ext "aaa" \
56 | --arg name "$github_user" \
57 | --argjson score "$total_succeeds" \
58 | --argjson totalScore "$total_exercations" \
59 | '{channel: $channel, courseId: $courseId, ext: $ext, name: $name, score: $score, totalScore: $totalScore}')
60 |
61 | # 保存新的 JSON 文件
62 | echo "$new_json" > $summary_file
63 |
64 | # 打印新的 JSON 文件到终端
65 | cat $summary_file
66 | - name: Post summary JSON to remote API
67 | run: |
68 | summary_file=".github/result/summary.json"
69 |
70 | # 发送 POST 请求
71 | curl -X POST "$url" \
72 | -H "accept: application/json;charset=utf-8" \
73 | -H "Content-Type: application/json" \
74 | -H "token: $token" \
75 | -d "$(cat $summary_file)" \
76 | -v
77 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.swp
2 | target/
3 | **/*.rs.bk
4 | .DS_Store
5 | *.pdb
6 | exercises/clippy/Cargo.toml
7 | exercises/clippy/Cargo.lock
8 | rust-project.json
9 | .idea
10 | .vscode/*
11 | !.vscode/extensions.json
12 | *.iml
13 | *.o
14 | public/
15 |
16 | # Local Netlify folder
17 | .netlify
18 |
--------------------------------------------------------------------------------
/.gitpod.yml:
--------------------------------------------------------------------------------
1 | tasks:
2 | - init: /workspace/rustlings/install.sh
3 | command: /workspace/.cargo/bin/rustlings watch
4 |
5 | vscode:
6 | extensions:
7 | - rust-lang.rust-analyzer@0.3.1348
8 |
--------------------------------------------------------------------------------
/.markdownlint.yml:
--------------------------------------------------------------------------------
1 | # MD013/line-length Line length, Expected: 80
2 | MD013: false
3 |
--------------------------------------------------------------------------------
/.vscode/extensions.json:
--------------------------------------------------------------------------------
1 | {
2 | "recommendations": [
3 | "rust-lang.rust-analyzer"
4 | ]
5 | }
6 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | ## Contributing to Rustlings
2 |
3 | First off, thanks for taking the time to contribute!! ❤️
4 |
5 | ### Quick Reference
6 |
7 | I want to...
8 |
9 | _add an exercise! ➡️ [read this](#addex) and then [open a Pull Request](#prs)_
10 |
11 | _update an outdated exercise! ➡️ [open a Pull Request](#prs)_
12 |
13 | _report a bug! ➡️ [open an Issue](#issues)_
14 |
15 | _fix a bug! ➡️ [open a Pull Request](#prs)_
16 |
17 | _implement a new feature! ➡️ [open an Issue to discuss it first, then a Pull Request](#issues)_
18 |
19 |
20 | ### Working on the source code
21 |
22 | `rustlings` is basically a glorified `rustc` wrapper. Therefore the source code
23 | isn't really that complicated since the bulk of the work is done by `rustc`.
24 | `src/main.rs` contains a simple `argh` CLI that connects to most of the other source files.
25 |
26 |
27 | ### Adding an exercise
28 |
29 | The first step is to add the exercise! Name the file `exercises/yourTopic/yourTopicN.rs`, make sure to
30 | put in some helpful links, and link to sections of the book in `exercises/yourTopic/README.md`.
31 |
32 | Next make sure it runs with `rustlings`. The exercise metadata is stored in `info.toml`, under the `exercises` array. The order of the `exercises` array determines the order the exercises are run by `rustlings verify` and `rustlings watch`.
33 |
34 | Add the metadata for your exercise in the correct order in the `exercises` array. If you are unsure of the correct ordering, add it at the bottom and ask in your pull request. The exercise metadata should contain the following:
35 | ```diff
36 | ...
37 | + [[exercises]]
38 | + name = "yourTopicN"
39 | + path = "exercises/yourTopic/yourTopicN.rs"
40 | + mode = "compile"
41 | + hint = """
42 | + Some kind of useful hint for your exercise."""
43 | ...
44 | ```
45 |
46 | The `mode` attribute decides whether Rustlings will only compile your exercise, or compile and test it. If you have tests to verify in your exercise, choose `test`, otherwise `compile`. If you're working on a Clippy exercise, use `mode = "clippy"`.
47 |
48 | That's all! Feel free to put up a pull request.
49 |
50 |
51 | ### Issues
52 |
53 | You can open an issue [here](https://github.com/rust-lang/rustlings/issues/new).
54 | If you're reporting a bug, please include the output of the following commands:
55 |
56 | - `rustc --version`
57 | - `rustlings --version`
58 | - `ls -la`
59 | - Your OS name and version
60 |
61 |
62 | ### Pull Requests
63 |
64 | Opening a pull request is as easy as forking the repository and committing your
65 | changes. There's a couple of things to watch out for:
66 |
67 | #### Write correct commit messages
68 |
69 | We follow the [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0-beta.4/)
70 | specification.
71 | This means that you have to format your commit messages in a specific way. Say
72 | you're working on adding a new exercise called `foobar1.rs`. You could write
73 | the following commit message:
74 |
75 | ```
76 | feat: add foobar1.rs exercise
77 | ```
78 |
79 | If you're just fixing a bug, please use the `fix` type:
80 |
81 | ```
82 | fix(verify): make sure verify doesn't self-destruct
83 | ```
84 |
85 | The scope within the brackets is optional, but should be any of these:
86 |
87 | - `installation` (for the installation script)
88 | - `cli` (for general CLI changes)
89 | - `verify` (for the verification source file)
90 | - `watch` (for the watch functionality source)
91 | - `run` (for the run functionality source)
92 | - `EXERCISENAME` (if you're changing a specific exercise, or set of exercises,
93 | substitute them here)
94 |
95 | When the commit also happens to close an existing issue, link it in the message
96 | body:
97 |
98 | ```
99 | fix: update foobar
100 |
101 | closes #101029908
102 | ```
103 |
104 | If you're doing simple changes, like updating a book link, use `chore`:
105 |
106 | ```
107 | chore: update exercise1.rs book link
108 | ```
109 |
110 | If you're updating documentation, use `docs`:
111 |
112 | ```
113 | docs: add more information to Readme
114 | ```
115 |
116 | If, and only if, you're absolutely sure you want to make a breaking change
117 | (please discuss this beforehand!), add an exclamation mark to the type and
118 | explain the breaking change in the message body:
119 |
120 | ```
121 | fix!: completely change verification
122 |
123 | BREAKING CHANGE: This has to be done because lorem ipsum dolor
124 | ```
125 |
126 | #### Pull Request Workflow
127 |
128 | Once you open a Pull Request, it may be reviewed or labeled (or both) until
129 | the maintainers accept your change. Please be patient, it may take some time
130 | for this to happen!
131 |
--------------------------------------------------------------------------------
/Cargo.lock:
--------------------------------------------------------------------------------
1 | # This file is automatically @generated by Cargo.
2 | # It is not intended for manual editing.
3 | version = 3
4 |
5 | [[package]]
6 | name = "addr2line"
7 | version = "0.20.0"
8 | source = "registry+https://github.com/rust-lang/crates.io-index"
9 | checksum = "f4fa78e18c64fce05e902adecd7a5eed15a5e0a3439f7b0e169f0252214865e3"
10 | dependencies = [
11 | "gimli",
12 | ]
13 |
14 | [[package]]
15 | name = "adler"
16 | version = "1.0.2"
17 | source = "registry+https://github.com/rust-lang/crates.io-index"
18 | checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
19 |
20 | [[package]]
21 | name = "aho-corasick"
22 | version = "1.0.2"
23 | source = "registry+https://github.com/rust-lang/crates.io-index"
24 | checksum = "43f6cb1bf222025340178f382c426f13757b2960e89779dfcb319c32542a5a41"
25 | dependencies = [
26 | "memchr",
27 | ]
28 |
29 | [[package]]
30 | name = "argh"
31 | version = "0.1.10"
32 | source = "registry+https://github.com/rust-lang/crates.io-index"
33 | checksum = "ab257697eb9496bf75526f0217b5ed64636a9cfafa78b8365c71bd283fcef93e"
34 | dependencies = [
35 | "argh_derive",
36 | "argh_shared",
37 | ]
38 |
39 | [[package]]
40 | name = "argh_derive"
41 | version = "0.1.10"
42 | source = "registry+https://github.com/rust-lang/crates.io-index"
43 | checksum = "b382dbd3288e053331f03399e1db106c9fb0d8562ad62cb04859ae926f324fa6"
44 | dependencies = [
45 | "argh_shared",
46 | "proc-macro2",
47 | "quote",
48 | "syn 1.0.109",
49 | ]
50 |
51 | [[package]]
52 | name = "argh_shared"
53 | version = "0.1.10"
54 | source = "registry+https://github.com/rust-lang/crates.io-index"
55 | checksum = "64cb94155d965e3d37ffbbe7cc5b82c3dd79dd33bd48e536f73d2cfb8d85506f"
56 |
57 | [[package]]
58 | name = "assert_cmd"
59 | version = "0.11.1"
60 | source = "registry+https://github.com/rust-lang/crates.io-index"
61 | checksum = "2dc477793bd82ec39799b6f6b3df64938532fdf2ab0d49ef817eac65856a5a1e"
62 | dependencies = [
63 | "escargot",
64 | "predicates",
65 | "predicates-core",
66 | "predicates-tree",
67 | ]
68 |
69 | [[package]]
70 | name = "autocfg"
71 | version = "1.1.0"
72 | source = "registry+https://github.com/rust-lang/crates.io-index"
73 | checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
74 |
75 | [[package]]
76 | name = "backtrace"
77 | version = "0.3.68"
78 | source = "registry+https://github.com/rust-lang/crates.io-index"
79 | checksum = "4319208da049c43661739c5fade2ba182f09d1dc2299b32298d3a31692b17e12"
80 | dependencies = [
81 | "addr2line",
82 | "cc",
83 | "cfg-if 1.0.0",
84 | "libc",
85 | "miniz_oxide",
86 | "object",
87 | "rustc-demangle",
88 | ]
89 |
90 | [[package]]
91 | name = "bitflags"
92 | version = "1.3.2"
93 | source = "registry+https://github.com/rust-lang/crates.io-index"
94 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
95 |
96 | [[package]]
97 | name = "bytes"
98 | version = "1.4.0"
99 | source = "registry+https://github.com/rust-lang/crates.io-index"
100 | checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be"
101 |
102 | [[package]]
103 | name = "cc"
104 | version = "1.0.79"
105 | source = "registry+https://github.com/rust-lang/crates.io-index"
106 | checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f"
107 |
108 | [[package]]
109 | name = "cfg-if"
110 | version = "0.1.10"
111 | source = "registry+https://github.com/rust-lang/crates.io-index"
112 | checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
113 |
114 | [[package]]
115 | name = "cfg-if"
116 | version = "1.0.0"
117 | source = "registry+https://github.com/rust-lang/crates.io-index"
118 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
119 |
120 | [[package]]
121 | name = "console"
122 | version = "0.15.7"
123 | source = "registry+https://github.com/rust-lang/crates.io-index"
124 | checksum = "c926e00cc70edefdc64d3a5ff31cc65bb97a3460097762bd23afb4d8145fccf8"
125 | dependencies = [
126 | "encode_unicode",
127 | "lazy_static",
128 | "libc",
129 | "unicode-width",
130 | "windows-sys 0.45.0",
131 | ]
132 |
133 | [[package]]
134 | name = "difference"
135 | version = "2.0.0"
136 | source = "registry+https://github.com/rust-lang/crates.io-index"
137 | checksum = "524cbf6897b527295dff137cec09ecf3a05f4fddffd7dfcd1585403449e74198"
138 |
139 | [[package]]
140 | name = "encode_unicode"
141 | version = "0.3.6"
142 | source = "registry+https://github.com/rust-lang/crates.io-index"
143 | checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f"
144 |
145 | [[package]]
146 | name = "escargot"
147 | version = "0.4.0"
148 | source = "registry+https://github.com/rust-lang/crates.io-index"
149 | checksum = "ceb9adbf9874d5d028b5e4c5739d22b71988252b25c9c98fe7cf9738bee84597"
150 | dependencies = [
151 | "lazy_static",
152 | "log",
153 | "serde",
154 | "serde_json",
155 | ]
156 |
157 | [[package]]
158 | name = "filetime"
159 | version = "0.2.21"
160 | source = "registry+https://github.com/rust-lang/crates.io-index"
161 | checksum = "5cbc844cecaee9d4443931972e1289c8ff485cb4cc2767cb03ca139ed6885153"
162 | dependencies = [
163 | "cfg-if 1.0.0",
164 | "libc",
165 | "redox_syscall 0.2.16",
166 | "windows-sys 0.48.0",
167 | ]
168 |
169 | [[package]]
170 | name = "float-cmp"
171 | version = "0.8.0"
172 | source = "registry+https://github.com/rust-lang/crates.io-index"
173 | checksum = "e1267f4ac4f343772758f7b1bdcbe767c218bbab93bb432acbf5162bbf85a6c4"
174 | dependencies = [
175 | "num-traits",
176 | ]
177 |
178 | [[package]]
179 | name = "fsevent"
180 | version = "0.4.0"
181 | source = "registry+https://github.com/rust-lang/crates.io-index"
182 | checksum = "5ab7d1bd1bd33cc98b0889831b72da23c0aa4df9cec7e0702f46ecea04b35db6"
183 | dependencies = [
184 | "bitflags",
185 | "fsevent-sys",
186 | ]
187 |
188 | [[package]]
189 | name = "fsevent-sys"
190 | version = "2.0.1"
191 | source = "registry+https://github.com/rust-lang/crates.io-index"
192 | checksum = "f41b048a94555da0f42f1d632e2e19510084fb8e303b0daa2816e733fb3644a0"
193 | dependencies = [
194 | "libc",
195 | ]
196 |
197 | [[package]]
198 | name = "fuchsia-zircon"
199 | version = "0.3.3"
200 | source = "registry+https://github.com/rust-lang/crates.io-index"
201 | checksum = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82"
202 | dependencies = [
203 | "bitflags",
204 | "fuchsia-zircon-sys",
205 | ]
206 |
207 | [[package]]
208 | name = "fuchsia-zircon-sys"
209 | version = "0.3.3"
210 | source = "registry+https://github.com/rust-lang/crates.io-index"
211 | checksum = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7"
212 |
213 | [[package]]
214 | name = "gimli"
215 | version = "0.27.3"
216 | source = "registry+https://github.com/rust-lang/crates.io-index"
217 | checksum = "b6c80984affa11d98d1b88b66ac8853f143217b399d3c74116778ff8fdb4ed2e"
218 |
219 | [[package]]
220 | name = "glob"
221 | version = "0.3.1"
222 | source = "registry+https://github.com/rust-lang/crates.io-index"
223 | checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
224 |
225 | [[package]]
226 | name = "hermit-abi"
227 | version = "0.3.2"
228 | source = "registry+https://github.com/rust-lang/crates.io-index"
229 | checksum = "443144c8cdadd93ebf52ddb4056d257f5b52c04d3c804e657d19eb73fc33668b"
230 |
231 | [[package]]
232 | name = "home"
233 | version = "0.5.5"
234 | source = "registry+https://github.com/rust-lang/crates.io-index"
235 | checksum = "5444c27eef6923071f7ebcc33e3444508466a76f7a2b93da00ed6e19f30c1ddb"
236 | dependencies = [
237 | "windows-sys 0.48.0",
238 | ]
239 |
240 | [[package]]
241 | name = "indicatif"
242 | version = "0.16.2"
243 | source = "registry+https://github.com/rust-lang/crates.io-index"
244 | checksum = "2d207dc617c7a380ab07ff572a6e52fa202a2a8f355860ac9c38e23f8196be1b"
245 | dependencies = [
246 | "console",
247 | "lazy_static",
248 | "number_prefix",
249 | "regex",
250 | ]
251 |
252 | [[package]]
253 | name = "inotify"
254 | version = "0.7.1"
255 | source = "registry+https://github.com/rust-lang/crates.io-index"
256 | checksum = "4816c66d2c8ae673df83366c18341538f234a26d65a9ecea5c348b453ac1d02f"
257 | dependencies = [
258 | "bitflags",
259 | "inotify-sys",
260 | "libc",
261 | ]
262 |
263 | [[package]]
264 | name = "inotify-sys"
265 | version = "0.1.5"
266 | source = "registry+https://github.com/rust-lang/crates.io-index"
267 | checksum = "e05c02b5e89bff3b946cedeca278abc628fe811e604f027c45a8aa3cf793d0eb"
268 | dependencies = [
269 | "libc",
270 | ]
271 |
272 | [[package]]
273 | name = "iovec"
274 | version = "0.1.4"
275 | source = "registry+https://github.com/rust-lang/crates.io-index"
276 | checksum = "b2b3ea6ff95e175473f8ffe6a7eb7c00d054240321b84c57051175fe3c1e075e"
277 | dependencies = [
278 | "libc",
279 | ]
280 |
281 | [[package]]
282 | name = "itoa"
283 | version = "1.0.8"
284 | source = "registry+https://github.com/rust-lang/crates.io-index"
285 | checksum = "62b02a5381cc465bd3041d84623d0fa3b66738b52b8e2fc3bab8ad63ab032f4a"
286 |
287 | [[package]]
288 | name = "kernel32-sys"
289 | version = "0.2.2"
290 | source = "registry+https://github.com/rust-lang/crates.io-index"
291 | checksum = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d"
292 | dependencies = [
293 | "winapi 0.2.8",
294 | "winapi-build",
295 | ]
296 |
297 | [[package]]
298 | name = "lazy_static"
299 | version = "1.4.0"
300 | source = "registry+https://github.com/rust-lang/crates.io-index"
301 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
302 |
303 | [[package]]
304 | name = "lazycell"
305 | version = "1.3.0"
306 | source = "registry+https://github.com/rust-lang/crates.io-index"
307 | checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
308 |
309 | [[package]]
310 | name = "libc"
311 | version = "0.2.147"
312 | source = "registry+https://github.com/rust-lang/crates.io-index"
313 | checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3"
314 |
315 | [[package]]
316 | name = "lock_api"
317 | version = "0.4.10"
318 | source = "registry+https://github.com/rust-lang/crates.io-index"
319 | checksum = "c1cc9717a20b1bb222f333e6a92fd32f7d8a18ddc5a3191a11af45dcbf4dcd16"
320 | dependencies = [
321 | "autocfg",
322 | "scopeguard",
323 | ]
324 |
325 | [[package]]
326 | name = "log"
327 | version = "0.4.19"
328 | source = "registry+https://github.com/rust-lang/crates.io-index"
329 | checksum = "b06a4cde4c0f271a446782e3eff8de789548ce57dbc8eca9292c27f4a42004b4"
330 |
331 | [[package]]
332 | name = "memchr"
333 | version = "2.5.0"
334 | source = "registry+https://github.com/rust-lang/crates.io-index"
335 | checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
336 |
337 | [[package]]
338 | name = "miniz_oxide"
339 | version = "0.7.1"
340 | source = "registry+https://github.com/rust-lang/crates.io-index"
341 | checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7"
342 | dependencies = [
343 | "adler",
344 | ]
345 |
346 | [[package]]
347 | name = "mio"
348 | version = "0.6.23"
349 | source = "registry+https://github.com/rust-lang/crates.io-index"
350 | checksum = "4afd66f5b91bf2a3bc13fad0e21caedac168ca4c707504e75585648ae80e4cc4"
351 | dependencies = [
352 | "cfg-if 0.1.10",
353 | "fuchsia-zircon",
354 | "fuchsia-zircon-sys",
355 | "iovec",
356 | "kernel32-sys",
357 | "libc",
358 | "log",
359 | "miow",
360 | "net2",
361 | "slab",
362 | "winapi 0.2.8",
363 | ]
364 |
365 | [[package]]
366 | name = "mio"
367 | version = "0.8.8"
368 | source = "registry+https://github.com/rust-lang/crates.io-index"
369 | checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2"
370 | dependencies = [
371 | "libc",
372 | "wasi",
373 | "windows-sys 0.48.0",
374 | ]
375 |
376 | [[package]]
377 | name = "mio-extras"
378 | version = "2.0.6"
379 | source = "registry+https://github.com/rust-lang/crates.io-index"
380 | checksum = "52403fe290012ce777c4626790c8951324a2b9e3316b3143779c72b029742f19"
381 | dependencies = [
382 | "lazycell",
383 | "log",
384 | "mio 0.6.23",
385 | "slab",
386 | ]
387 |
388 | [[package]]
389 | name = "miow"
390 | version = "0.2.2"
391 | source = "registry+https://github.com/rust-lang/crates.io-index"
392 | checksum = "ebd808424166322d4a38da87083bfddd3ac4c131334ed55856112eb06d46944d"
393 | dependencies = [
394 | "kernel32-sys",
395 | "net2",
396 | "winapi 0.2.8",
397 | "ws2_32-sys",
398 | ]
399 |
400 | [[package]]
401 | name = "net2"
402 | version = "0.2.39"
403 | source = "registry+https://github.com/rust-lang/crates.io-index"
404 | checksum = "b13b648036a2339d06de780866fbdfda0dde886de7b3af2ddeba8b14f4ee34ac"
405 | dependencies = [
406 | "cfg-if 0.1.10",
407 | "libc",
408 | "winapi 0.3.9",
409 | ]
410 |
411 | [[package]]
412 | name = "normalize-line-endings"
413 | version = "0.3.0"
414 | source = "registry+https://github.com/rust-lang/crates.io-index"
415 | checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be"
416 |
417 | [[package]]
418 | name = "notify"
419 | version = "4.0.17"
420 | source = "registry+https://github.com/rust-lang/crates.io-index"
421 | checksum = "ae03c8c853dba7bfd23e571ff0cff7bc9dceb40a4cd684cd1681824183f45257"
422 | dependencies = [
423 | "bitflags",
424 | "filetime",
425 | "fsevent",
426 | "fsevent-sys",
427 | "inotify",
428 | "libc",
429 | "mio 0.6.23",
430 | "mio-extras",
431 | "walkdir",
432 | "winapi 0.3.9",
433 | ]
434 |
435 | [[package]]
436 | name = "num-traits"
437 | version = "0.2.15"
438 | source = "registry+https://github.com/rust-lang/crates.io-index"
439 | checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd"
440 | dependencies = [
441 | "autocfg",
442 | ]
443 |
444 | [[package]]
445 | name = "num_cpus"
446 | version = "1.16.0"
447 | source = "registry+https://github.com/rust-lang/crates.io-index"
448 | checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43"
449 | dependencies = [
450 | "hermit-abi",
451 | "libc",
452 | ]
453 |
454 | [[package]]
455 | name = "number_prefix"
456 | version = "0.4.0"
457 | source = "registry+https://github.com/rust-lang/crates.io-index"
458 | checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3"
459 |
460 | [[package]]
461 | name = "object"
462 | version = "0.31.1"
463 | source = "registry+https://github.com/rust-lang/crates.io-index"
464 | checksum = "8bda667d9f2b5051b8833f59f3bf748b28ef54f850f4fcb389a252aa383866d1"
465 | dependencies = [
466 | "memchr",
467 | ]
468 |
469 | [[package]]
470 | name = "parking_lot"
471 | version = "0.12.1"
472 | source = "registry+https://github.com/rust-lang/crates.io-index"
473 | checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f"
474 | dependencies = [
475 | "lock_api",
476 | "parking_lot_core",
477 | ]
478 |
479 | [[package]]
480 | name = "parking_lot_core"
481 | version = "0.9.8"
482 | source = "registry+https://github.com/rust-lang/crates.io-index"
483 | checksum = "93f00c865fe7cabf650081affecd3871070f26767e7b2070a3ffae14c654b447"
484 | dependencies = [
485 | "cfg-if 1.0.0",
486 | "libc",
487 | "redox_syscall 0.3.5",
488 | "smallvec",
489 | "windows-targets 0.48.1",
490 | ]
491 |
492 | [[package]]
493 | name = "pin-project-lite"
494 | version = "0.2.10"
495 | source = "registry+https://github.com/rust-lang/crates.io-index"
496 | checksum = "4c40d25201921e5ff0c862a505c6557ea88568a4e3ace775ab55e93f2f4f9d57"
497 |
498 | [[package]]
499 | name = "predicates"
500 | version = "1.0.8"
501 | source = "registry+https://github.com/rust-lang/crates.io-index"
502 | checksum = "f49cfaf7fdaa3bfacc6fa3e7054e65148878354a5cfddcf661df4c851f8021df"
503 | dependencies = [
504 | "difference",
505 | "float-cmp",
506 | "normalize-line-endings",
507 | "predicates-core",
508 | "regex",
509 | ]
510 |
511 | [[package]]
512 | name = "predicates-core"
513 | version = "1.0.6"
514 | source = "registry+https://github.com/rust-lang/crates.io-index"
515 | checksum = "b794032607612e7abeb4db69adb4e33590fa6cf1149e95fd7cb00e634b92f174"
516 |
517 | [[package]]
518 | name = "predicates-tree"
519 | version = "1.0.9"
520 | source = "registry+https://github.com/rust-lang/crates.io-index"
521 | checksum = "368ba315fb8c5052ab692e68a0eefec6ec57b23a36959c14496f0b0df2c0cecf"
522 | dependencies = [
523 | "predicates-core",
524 | "termtree",
525 | ]
526 |
527 | [[package]]
528 | name = "proc-macro2"
529 | version = "1.0.64"
530 | source = "registry+https://github.com/rust-lang/crates.io-index"
531 | checksum = "78803b62cbf1f46fde80d7c0e803111524b9877184cfe7c3033659490ac7a7da"
532 | dependencies = [
533 | "unicode-ident",
534 | ]
535 |
536 | [[package]]
537 | name = "quote"
538 | version = "1.0.29"
539 | source = "registry+https://github.com/rust-lang/crates.io-index"
540 | checksum = "573015e8ab27661678357f27dc26460738fd2b6c86e46f386fde94cb5d913105"
541 | dependencies = [
542 | "proc-macro2",
543 | ]
544 |
545 | [[package]]
546 | name = "redox_syscall"
547 | version = "0.2.16"
548 | source = "registry+https://github.com/rust-lang/crates.io-index"
549 | checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a"
550 | dependencies = [
551 | "bitflags",
552 | ]
553 |
554 | [[package]]
555 | name = "redox_syscall"
556 | version = "0.3.5"
557 | source = "registry+https://github.com/rust-lang/crates.io-index"
558 | checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29"
559 | dependencies = [
560 | "bitflags",
561 | ]
562 |
563 | [[package]]
564 | name = "regex"
565 | version = "1.9.1"
566 | source = "registry+https://github.com/rust-lang/crates.io-index"
567 | checksum = "b2eae68fc220f7cf2532e4494aded17545fce192d59cd996e0fe7887f4ceb575"
568 | dependencies = [
569 | "aho-corasick",
570 | "memchr",
571 | "regex-automata",
572 | "regex-syntax",
573 | ]
574 |
575 | [[package]]
576 | name = "regex-automata"
577 | version = "0.3.3"
578 | source = "registry+https://github.com/rust-lang/crates.io-index"
579 | checksum = "39354c10dd07468c2e73926b23bb9c2caca74c5501e38a35da70406f1d923310"
580 | dependencies = [
581 | "aho-corasick",
582 | "memchr",
583 | "regex-syntax",
584 | ]
585 |
586 | [[package]]
587 | name = "regex-syntax"
588 | version = "0.7.4"
589 | source = "registry+https://github.com/rust-lang/crates.io-index"
590 | checksum = "e5ea92a5b6195c6ef2a0295ea818b312502c6fc94dde986c5553242e18fd4ce2"
591 |
592 | [[package]]
593 | name = "rustc-demangle"
594 | version = "0.1.23"
595 | source = "registry+https://github.com/rust-lang/crates.io-index"
596 | checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76"
597 |
598 | [[package]]
599 | name = "rustlings"
600 | version = "5.5.1"
601 | dependencies = [
602 | "argh",
603 | "assert_cmd",
604 | "console",
605 | "glob",
606 | "home",
607 | "indicatif",
608 | "notify",
609 | "predicates",
610 | "regex",
611 | "serde",
612 | "serde_json",
613 | "tokio",
614 | "toml",
615 | ]
616 |
617 | [[package]]
618 | name = "ryu"
619 | version = "1.0.14"
620 | source = "registry+https://github.com/rust-lang/crates.io-index"
621 | checksum = "fe232bdf6be8c8de797b22184ee71118d63780ea42ac85b61d1baa6d3b782ae9"
622 |
623 | [[package]]
624 | name = "same-file"
625 | version = "1.0.6"
626 | source = "registry+https://github.com/rust-lang/crates.io-index"
627 | checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
628 | dependencies = [
629 | "winapi-util",
630 | ]
631 |
632 | [[package]]
633 | name = "scopeguard"
634 | version = "1.2.0"
635 | source = "registry+https://github.com/rust-lang/crates.io-index"
636 | checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
637 |
638 | [[package]]
639 | name = "serde"
640 | version = "1.0.171"
641 | source = "registry+https://github.com/rust-lang/crates.io-index"
642 | checksum = "30e27d1e4fd7659406c492fd6cfaf2066ba8773de45ca75e855590f856dc34a9"
643 | dependencies = [
644 | "serde_derive",
645 | ]
646 |
647 | [[package]]
648 | name = "serde_derive"
649 | version = "1.0.171"
650 | source = "registry+https://github.com/rust-lang/crates.io-index"
651 | checksum = "389894603bd18c46fa56231694f8d827779c0951a667087194cf9de94ed24682"
652 | dependencies = [
653 | "proc-macro2",
654 | "quote",
655 | "syn 2.0.25",
656 | ]
657 |
658 | [[package]]
659 | name = "serde_json"
660 | version = "1.0.102"
661 | source = "registry+https://github.com/rust-lang/crates.io-index"
662 | checksum = "b5062a995d481b2308b6064e9af76011f2921c35f97b0468811ed9f6cd91dfed"
663 | dependencies = [
664 | "itoa",
665 | "ryu",
666 | "serde",
667 | ]
668 |
669 | [[package]]
670 | name = "signal-hook-registry"
671 | version = "1.4.1"
672 | source = "registry+https://github.com/rust-lang/crates.io-index"
673 | checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1"
674 | dependencies = [
675 | "libc",
676 | ]
677 |
678 | [[package]]
679 | name = "slab"
680 | version = "0.4.8"
681 | source = "registry+https://github.com/rust-lang/crates.io-index"
682 | checksum = "6528351c9bc8ab22353f9d776db39a20288e8d6c37ef8cfe3317cf875eecfc2d"
683 | dependencies = [
684 | "autocfg",
685 | ]
686 |
687 | [[package]]
688 | name = "smallvec"
689 | version = "1.11.0"
690 | source = "registry+https://github.com/rust-lang/crates.io-index"
691 | checksum = "62bb4feee49fdd9f707ef802e22365a35de4b7b299de4763d44bfea899442ff9"
692 |
693 | [[package]]
694 | name = "socket2"
695 | version = "0.4.9"
696 | source = "registry+https://github.com/rust-lang/crates.io-index"
697 | checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662"
698 | dependencies = [
699 | "libc",
700 | "winapi 0.3.9",
701 | ]
702 |
703 | [[package]]
704 | name = "syn"
705 | version = "1.0.109"
706 | source = "registry+https://github.com/rust-lang/crates.io-index"
707 | checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
708 | dependencies = [
709 | "proc-macro2",
710 | "quote",
711 | "unicode-ident",
712 | ]
713 |
714 | [[package]]
715 | name = "syn"
716 | version = "2.0.25"
717 | source = "registry+https://github.com/rust-lang/crates.io-index"
718 | checksum = "15e3fc8c0c74267e2df136e5e5fb656a464158aa57624053375eb9c8c6e25ae2"
719 | dependencies = [
720 | "proc-macro2",
721 | "quote",
722 | "unicode-ident",
723 | ]
724 |
725 | [[package]]
726 | name = "termtree"
727 | version = "0.4.1"
728 | source = "registry+https://github.com/rust-lang/crates.io-index"
729 | checksum = "3369f5ac52d5eb6ab48c6b4ffdc8efbcad6b89c765749064ba298f2c68a16a76"
730 |
731 | [[package]]
732 | name = "tokio"
733 | version = "1.29.1"
734 | source = "registry+https://github.com/rust-lang/crates.io-index"
735 | checksum = "532826ff75199d5833b9d2c5fe410f29235e25704ee5f0ef599fb51c21f4a4da"
736 | dependencies = [
737 | "autocfg",
738 | "backtrace",
739 | "bytes",
740 | "libc",
741 | "mio 0.8.8",
742 | "num_cpus",
743 | "parking_lot",
744 | "pin-project-lite",
745 | "signal-hook-registry",
746 | "socket2",
747 | "tokio-macros",
748 | "windows-sys 0.48.0",
749 | ]
750 |
751 | [[package]]
752 | name = "tokio-macros"
753 | version = "2.1.0"
754 | source = "registry+https://github.com/rust-lang/crates.io-index"
755 | checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e"
756 | dependencies = [
757 | "proc-macro2",
758 | "quote",
759 | "syn 2.0.25",
760 | ]
761 |
762 | [[package]]
763 | name = "toml"
764 | version = "0.5.11"
765 | source = "registry+https://github.com/rust-lang/crates.io-index"
766 | checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234"
767 | dependencies = [
768 | "serde",
769 | ]
770 |
771 | [[package]]
772 | name = "unicode-ident"
773 | version = "1.0.10"
774 | source = "registry+https://github.com/rust-lang/crates.io-index"
775 | checksum = "22049a19f4a68748a168c0fc439f9516686aa045927ff767eca0a85101fb6e73"
776 |
777 | [[package]]
778 | name = "unicode-width"
779 | version = "0.1.10"
780 | source = "registry+https://github.com/rust-lang/crates.io-index"
781 | checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b"
782 |
783 | [[package]]
784 | name = "walkdir"
785 | version = "2.3.3"
786 | source = "registry+https://github.com/rust-lang/crates.io-index"
787 | checksum = "36df944cda56c7d8d8b7496af378e6b16de9284591917d307c9b4d313c44e698"
788 | dependencies = [
789 | "same-file",
790 | "winapi-util",
791 | ]
792 |
793 | [[package]]
794 | name = "wasi"
795 | version = "0.11.0+wasi-snapshot-preview1"
796 | source = "registry+https://github.com/rust-lang/crates.io-index"
797 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
798 |
799 | [[package]]
800 | name = "winapi"
801 | version = "0.2.8"
802 | source = "registry+https://github.com/rust-lang/crates.io-index"
803 | checksum = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a"
804 |
805 | [[package]]
806 | name = "winapi"
807 | version = "0.3.9"
808 | source = "registry+https://github.com/rust-lang/crates.io-index"
809 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
810 | dependencies = [
811 | "winapi-i686-pc-windows-gnu",
812 | "winapi-x86_64-pc-windows-gnu",
813 | ]
814 |
815 | [[package]]
816 | name = "winapi-build"
817 | version = "0.1.1"
818 | source = "registry+https://github.com/rust-lang/crates.io-index"
819 | checksum = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc"
820 |
821 | [[package]]
822 | name = "winapi-i686-pc-windows-gnu"
823 | version = "0.4.0"
824 | source = "registry+https://github.com/rust-lang/crates.io-index"
825 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
826 |
827 | [[package]]
828 | name = "winapi-util"
829 | version = "0.1.5"
830 | source = "registry+https://github.com/rust-lang/crates.io-index"
831 | checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
832 | dependencies = [
833 | "winapi 0.3.9",
834 | ]
835 |
836 | [[package]]
837 | name = "winapi-x86_64-pc-windows-gnu"
838 | version = "0.4.0"
839 | source = "registry+https://github.com/rust-lang/crates.io-index"
840 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
841 |
842 | [[package]]
843 | name = "windows-sys"
844 | version = "0.45.0"
845 | source = "registry+https://github.com/rust-lang/crates.io-index"
846 | checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0"
847 | dependencies = [
848 | "windows-targets 0.42.2",
849 | ]
850 |
851 | [[package]]
852 | name = "windows-sys"
853 | version = "0.48.0"
854 | source = "registry+https://github.com/rust-lang/crates.io-index"
855 | checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
856 | dependencies = [
857 | "windows-targets 0.48.1",
858 | ]
859 |
860 | [[package]]
861 | name = "windows-targets"
862 | version = "0.42.2"
863 | source = "registry+https://github.com/rust-lang/crates.io-index"
864 | checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071"
865 | dependencies = [
866 | "windows_aarch64_gnullvm 0.42.2",
867 | "windows_aarch64_msvc 0.42.2",
868 | "windows_i686_gnu 0.42.2",
869 | "windows_i686_msvc 0.42.2",
870 | "windows_x86_64_gnu 0.42.2",
871 | "windows_x86_64_gnullvm 0.42.2",
872 | "windows_x86_64_msvc 0.42.2",
873 | ]
874 |
875 | [[package]]
876 | name = "windows-targets"
877 | version = "0.48.1"
878 | source = "registry+https://github.com/rust-lang/crates.io-index"
879 | checksum = "05d4b17490f70499f20b9e791dcf6a299785ce8af4d709018206dc5b4953e95f"
880 | dependencies = [
881 | "windows_aarch64_gnullvm 0.48.0",
882 | "windows_aarch64_msvc 0.48.0",
883 | "windows_i686_gnu 0.48.0",
884 | "windows_i686_msvc 0.48.0",
885 | "windows_x86_64_gnu 0.48.0",
886 | "windows_x86_64_gnullvm 0.48.0",
887 | "windows_x86_64_msvc 0.48.0",
888 | ]
889 |
890 | [[package]]
891 | name = "windows_aarch64_gnullvm"
892 | version = "0.42.2"
893 | source = "registry+https://github.com/rust-lang/crates.io-index"
894 | checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8"
895 |
896 | [[package]]
897 | name = "windows_aarch64_gnullvm"
898 | version = "0.48.0"
899 | source = "registry+https://github.com/rust-lang/crates.io-index"
900 | checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc"
901 |
902 | [[package]]
903 | name = "windows_aarch64_msvc"
904 | version = "0.42.2"
905 | source = "registry+https://github.com/rust-lang/crates.io-index"
906 | checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43"
907 |
908 | [[package]]
909 | name = "windows_aarch64_msvc"
910 | version = "0.48.0"
911 | source = "registry+https://github.com/rust-lang/crates.io-index"
912 | checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3"
913 |
914 | [[package]]
915 | name = "windows_i686_gnu"
916 | version = "0.42.2"
917 | source = "registry+https://github.com/rust-lang/crates.io-index"
918 | checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f"
919 |
920 | [[package]]
921 | name = "windows_i686_gnu"
922 | version = "0.48.0"
923 | source = "registry+https://github.com/rust-lang/crates.io-index"
924 | checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241"
925 |
926 | [[package]]
927 | name = "windows_i686_msvc"
928 | version = "0.42.2"
929 | source = "registry+https://github.com/rust-lang/crates.io-index"
930 | checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060"
931 |
932 | [[package]]
933 | name = "windows_i686_msvc"
934 | version = "0.48.0"
935 | source = "registry+https://github.com/rust-lang/crates.io-index"
936 | checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00"
937 |
938 | [[package]]
939 | name = "windows_x86_64_gnu"
940 | version = "0.42.2"
941 | source = "registry+https://github.com/rust-lang/crates.io-index"
942 | checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36"
943 |
944 | [[package]]
945 | name = "windows_x86_64_gnu"
946 | version = "0.48.0"
947 | source = "registry+https://github.com/rust-lang/crates.io-index"
948 | checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1"
949 |
950 | [[package]]
951 | name = "windows_x86_64_gnullvm"
952 | version = "0.42.2"
953 | source = "registry+https://github.com/rust-lang/crates.io-index"
954 | checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3"
955 |
956 | [[package]]
957 | name = "windows_x86_64_gnullvm"
958 | version = "0.48.0"
959 | source = "registry+https://github.com/rust-lang/crates.io-index"
960 | checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953"
961 |
962 | [[package]]
963 | name = "windows_x86_64_msvc"
964 | version = "0.42.2"
965 | source = "registry+https://github.com/rust-lang/crates.io-index"
966 | checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0"
967 |
968 | [[package]]
969 | name = "windows_x86_64_msvc"
970 | version = "0.48.0"
971 | source = "registry+https://github.com/rust-lang/crates.io-index"
972 | checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a"
973 |
974 | [[package]]
975 | name = "ws2_32-sys"
976 | version = "0.2.1"
977 | source = "registry+https://github.com/rust-lang/crates.io-index"
978 | checksum = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e"
979 | dependencies = [
980 | "winapi 0.2.8",
981 | "winapi-build",
982 | ]
983 |
--------------------------------------------------------------------------------
/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "rustlings"
3 | description = "Small exercises to get you used to reading and writing Rust code!"
4 | version = "5.5.1"
5 | authors = [
6 | "Liv ",
7 | "Carol (Nichols || Goulding) ",
8 | ]
9 | edition = "2021"
10 |
11 | [dependencies]
12 | argh = "0.1"
13 | indicatif = "0.16"
14 | console = "0.15"
15 | notify = "4.0"
16 | toml = "0.5"
17 | regex = "1.5"
18 | serde = { version = "1.0", features = ["derive"] }
19 | serde_json = "1.0.81"
20 | home = "0.5.3"
21 | glob = "0.3.0"
22 | tokio = { version = "1.21.2", features = ["full"] }
23 |
24 | [[bin]]
25 | name = "rustlings"
26 | path = "src/main.rs"
27 |
28 | [dev-dependencies]
29 | assert_cmd = "0.11.0"
30 | predicates = "1.0.1"
31 | glob = "0.3.0"
32 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2016 Carol (Nichols || Goulding)
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
23 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## 第一期 Rust 入门训练营-基础阶段
2 |
3 |
4 |
5 | 基础阶段实验同样将通过Rustlings进行测试,请按照以下步骤进行练习:
6 |
7 | 1. 在网络浏览器中用自己的 github id 登录 github.com。
8 | 2. **fork 本仓库**,并按照下述引导进行答题,**在完成全部题目后请凭借排行榜成绩截图联系班主任进入专业阶段学习群**:
9 | * 本地环境:
10 | 1. **安装Linux的环境**。对于windows的用户,推荐使用wsl2安装Ubuntu 22.04,也可以使用vmware等虚拟机进行安装。如果在这一步存在问题,请联系助教。
11 | 2. **创建ssh key,用于ssh方式克隆github代码**。在linux环境下,使用`ssh-keygen -t rsa -b 4096 -C "你的邮箱"`命令,创建ssh key,下面的选项全部直接敲回车即可。 随后使用` cat ~/.ssh/id_rsa.pub` 命令查看生成的公钥,并完整的复制下来。 在github仓库界面点击自己的头像,选择`settings`。进入到设置页面后,点击左侧的`SSH and GPG keys`选项。点击`New SSH key`选项,并将复制下来的内容粘贴上去,添加该ssh key的描述。随后点击`Add SSH key`,并一路点击确认即可。
12 | 3. **本地安装rust**。进入linux环境下,参考Arceos 教程 [Rust 开发环境配置 - ArceOS Tutorial Book (rcore-os.cn)](https://rcore-os.cn/arceos-tutorial-book/ch01-02.html) 中,找到Rust 开发环境配置的章节,相应配置即可,你可以同时将后续需要的环境也配置好.
13 | 4. **clone实验仓库到本地。**在前面点击链接生成的仓库中,同样点击醒目的 `code` 绿色按钮,选择`local`下的`ssh`选项,复制下面的链接。随后回到本地linux环境下,使用`git clone 复制的链接`的方式,将目标仓库clone到本地。随后,使用`ls`命令查看自己clone下来的文件夹,再使用`cd`命令进入到该文件夹下,使用 `cargo install --force --path .` 安装rustlings。
14 | 5. **练习rustlings**。使用vscode等编辑器,进入clone下来的目录下的`exercises`文件夹,执行`rustlings watch`依次查看完成情况,并依次完成对应的练习。 执行`rustlings run 练习名称`去运行对应练习,也可以使用`rustlings hint 练习名称`查看题解。
15 | 6. **提交完成情况**。当做完部分或所有练习之后,在rustlings目录下执行 `git add .; git commit -m "update"; git push` 命令,把更新提交到GithubClassroom的CI进行自动评测。你可以在github仓库页面的actions页面,看到你的CI提交结果,或者 https://opencamp.ai/Rust/camp/S01/stage/1?tab=rank上面查看自己的评分。
16 | * 在线环境:
17 |
18 | 1. 如果使用在线环境,在本网页的中上部可以看到一个醒目的 `code` 绿色按钮,点击后,可以进一步看到 `codespace` 标签和醒目的 `create codesapce on main` 绿色按钮。请点击这个绿色按钮,就可以进入到在线的ubuntu +vscode环境中
19 |
20 | 1. 再按照下面的环境安装提示在vscode的 `console` 中安装配置开发环境:rustc等工具。
21 |
22 | 3. 然后就可以基于在线vscode进行测试 (执行命令 `rustlings watch` ),编辑代码的循环实验过程了。
23 |
24 | 3. 上述步骤有任何问题都可以找助教。
25 |
26 | 4. 下面是官方的Rustlings的布置,可以参考,**请务必不要拉取下面的仓库!**
27 |
28 | # rustlings 🦀❤️
29 |
30 |
31 |
32 | Greetings and welcome to `rustlings`. This project contains small exercises to get you used to reading and writing Rust code. This includes reading and responding to compiler messages!
33 |
34 | _...looking for the old, web-based version of Rustlings? Try [here](https://github.com/rust-lang/rustlings/tree/rustlings-1)_
35 |
36 | Alternatively, for a first-time Rust learner, there are several other resources:
37 |
38 | - [The Book](https://doc.rust-lang.org/book/index.html) - The most comprehensive resource for learning Rust, but a bit theoretical sometimes. You will be using this along with Rustlings!
39 | - [Rust By Example](https://doc.rust-lang.org/rust-by-example/index.html) - Learn Rust by solving little exercises! It's almost like `rustlings`, but online
40 |
41 | ## Getting Started
42 |
43 | _Note: If you're on MacOS, make sure you've installed Xcode and its developer tools by typing `xcode-select --install`._
44 | _Note: If you're on Linux, make sure you've installed gcc. Deb: `sudo apt install gcc`. Yum: `sudo yum -y install gcc`._
45 |
46 | You will need to have Rust installed. You can get it by visiting https://rustup.rs. This'll also install Cargo, Rust's package/project manager.
47 |
48 | ## MacOS/Linux
49 |
50 | Just run:
51 |
52 | ```bash
53 | curl -L https://raw.githubusercontent.com/rust-lang/rustlings/main/install.sh | bash
54 | ```
55 | Or if you want it to be installed to a different path:
56 |
57 | ```bash
58 | curl -L https://raw.githubusercontent.com/rust-lang/rustlings/main/install.sh | bash -s mypath/
59 | ```
60 |
61 | This will install Rustlings and give you access to the `rustlings` command. Run it to get started!
62 |
63 | ### Nix
64 |
65 | Basically: Clone the repository at the latest tag, finally run `nix develop` or `nix-shell`.
66 |
67 | ```bash
68 | # find out the latest version at https://github.com/rust-lang/rustlings/releases/latest (on edit 5.5.1)
69 | git clone -b 5.5.1 --depth 1 https://github.com/rust-lang/rustlings
70 | cd rustlings
71 | # if nix version > 2.3
72 | nix develop
73 | # if nix version <= 2.3
74 | nix-shell
75 | ```
76 |
77 | ## Windows
78 |
79 | In PowerShell (Run as Administrator), set `ExecutionPolicy` to `RemoteSigned`:
80 |
81 | ```ps1
82 | Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser
83 | ```
84 |
85 | Then, you can run:
86 |
87 | ```ps1
88 | Start-BitsTransfer -Source https://raw.githubusercontent.com/rust-lang/rustlings/main/install.ps1 -Destination $env:TMP/install_rustlings.ps1; Unblock-File $env:TMP/install_rustlings.ps1; Invoke-Expression $env:TMP/install_rustlings.ps1
89 | ```
90 |
91 | To install Rustlings. Same as on MacOS/Linux, you will have access to the `rustlings` command after it. Keep in mind that this works best in PowerShell, and any other terminals may give you errors.
92 |
93 | If you get a permission denied message, you might have to exclude the directory where you cloned Rustlings in your antivirus.
94 |
95 | ## Browser
96 |
97 | [](https://gitpod.io/#https://github.com/rust-lang/rustlings)
98 |
99 | [](https://github.com/codespaces/new/?repo=rust-lang%2Frustlings&ref=main)
100 |
101 | ## Manually
102 |
103 | Basically: Clone the repository at the latest tag, run `cargo install --path .`.
104 |
105 | ```bash
106 | # find out the latest version at https://github.com/rust-lang/rustlings/releases/latest (on edit 5.5.1)
107 | git clone -b 5.5.1 --depth 1 https://github.com/rust-lang/rustlings
108 | cd rustlings
109 | cargo install --force --path .
110 | ```
111 |
112 | If there are installation errors, ensure that your toolchain is up to date. For the latest, run:
113 |
114 | ```bash
115 | rustup update
116 | ```
117 |
118 | Then, same as above, run `rustlings` to get started.
119 |
120 | ## Doing exercises
121 |
122 | The exercises are sorted by topic and can be found in the subdirectory `rustlings/exercises/`. For every topic there is an additional README file with some resources to get you started on the topic. We really recommend that you have a look at them before you start.
123 |
124 | The task is simple. Most exercises contain an error that keeps them from compiling, and it's up to you to fix it! Some exercises are also run as tests, but rustlings handles them all the same. To run the exercises in the recommended order, execute:
125 |
126 | ```bash
127 | rustlings watch
128 | ```
129 |
130 | This will try to verify the completion of every exercise in a predetermined order (what we think is best for newcomers). It will also rerun automatically every time you change a file in the `exercises/` directory. If you want to only run it once, you can use:
131 |
132 | ```bash
133 | rustlings verify
134 | ```
135 |
136 | This will do the same as watch, but it'll quit after running.
137 |
138 | In case you want to go by your own order, or want to only verify a single exercise, you can run:
139 |
140 | ```bash
141 | rustlings run myExercise1
142 | ```
143 |
144 | Or simply use the following command to run the next unsolved exercise in the course:
145 |
146 | ```bash
147 | rustlings run next
148 | ```
149 |
150 | In case you get stuck, you can run the following command to get a hint for your
151 | exercise:
152 |
153 | ```bash
154 | rustlings hint myExercise1
155 | ```
156 |
157 | You can also get the hint for the next unsolved exercise with the following command:
158 |
159 | ```bash
160 | rustlings hint next
161 | ```
162 |
163 | To check your progress, you can run the following command:
164 |
165 | ```bash
166 | rustlings list
167 | ```
168 |
169 | ## Testing yourself
170 |
171 | After every couple of sections, there will be a quiz that'll test your knowledge on a bunch of sections at once. These quizzes are found in `exercises/quizN.rs`.
172 |
173 | ## Enabling `rust-analyzer`
174 |
175 | Run the command `rustlings lsp` which will generate a `rust-project.json` at the root of the project, this allows [rust-analyzer](https://rust-analyzer.github.io/) to parse each exercise.
176 |
177 | ## Continuing On
178 |
179 | Once you've completed Rustlings, put your new knowledge to good use! Continue practicing your Rust skills by building your own projects, contributing to Rustlings, or finding other open-source projects to contribute to.
180 |
181 | ## Uninstalling Rustlings
182 |
183 | If you want to remove Rustlings from your system, there are two steps. First, you'll need to remove the exercises folder that the install script created
184 | for you:
185 |
186 | ```bash
187 | rm -rf rustlings # or your custom folder name, if you chose and or renamed it
188 | ```
189 |
190 | Second, run `cargo uninstall` to remove the `rustlings` binary:
191 |
192 | ```bash
193 | cargo uninstall rustlings
194 | ```
195 |
196 | Now you should be done!
197 |
198 | ## Contributing
199 |
200 | See [CONTRIBUTING.md](./CONTRIBUTING.md).
201 |
202 | Development-focused discussion about Rustlings happens in the [**rustlings** stream](https://rust-lang.zulipchat.com/#narrow/stream/334454-rustlings)
203 | on the [Rust Project Zulip](https://rust-lang.zulipchat.com). Feel free to start a new thread there
204 | if you have ideas or suggestions!
205 |
206 | ## Contributors ✨
207 |
208 | Thanks goes to the wonderful people listed in [AUTHORS.md](./AUTHORS.md) 🎉
209 |
--------------------------------------------------------------------------------
/exercises/README.md:
--------------------------------------------------------------------------------
1 | # Exercise to Book Chapter mapping
2 |
3 | | Exercise | Book Chapter |
4 | | ---------------------- | ------------------- |
5 | | variables | §3.1 |
6 | | functions | §3.3 |
7 | | if | §3.5 |
8 | | primitive_types | §3.2, §4.3 |
9 | | vecs | §8.1 |
10 | | move_semantics | §4.1-2 |
11 | | structs | §5.1, §5.3 |
12 | | enums | §6, §18.3 |
13 | | strings | §8.2 |
14 | | modules | §7 |
15 | | hashmaps | §8.3 |
16 | | options | §10.1 |
17 | | error_handling | §9 |
18 | | generics | §10 |
19 | | traits | §10.2 |
20 | | tests | §11.1 |
21 | | lifetimes | §10.3 |
22 | | iterators | §13.2-4 |
23 | | threads | §16.1-3 |
24 | | smart_pointers | §15, §16.3 |
25 | | macros | §19.6 |
26 | | clippy | §21.4 |
27 | | conversions | n/a |
28 |
--------------------------------------------------------------------------------
/exercises/generics/README.md:
--------------------------------------------------------------------------------
1 | # Generics
2 |
3 | Generics is the topic of generalizing types and functionalities to broader cases.
4 | This is extremely useful for reducing code duplication in many ways, but can call for rather involving syntax.
5 | Namely, being generic requires taking great care to specify over which types a generic type is actually considered valid.
6 | The simplest and most common use of generics is for type parameters.
7 |
8 | ## Further information
9 |
10 | - [Generic Data Types](https://doc.rust-lang.org/stable/book/ch10-01-syntax.html)
11 | - [Bounds](https://doc.rust-lang.org/rust-by-example/generics/bounds.html)
12 |
--------------------------------------------------------------------------------
/exercises/generics/generics1.rs:
--------------------------------------------------------------------------------
1 | // generics1.rs
2 | //
3 | // This shopping list program isn't compiling! Use your knowledge of generics to
4 | // fix it.
5 | //
6 | // Execute `rustlings hint generics1` or use the `hint` watch subcommand for a
7 | // hint.
8 |
9 | // I AM NOT DONE
10 |
11 | fn main() {
12 | let mut shopping_list: Vec> = Vec::new();
13 | shopping_list.push("milk");
14 | }
15 |
--------------------------------------------------------------------------------
/exercises/generics/generics2.rs:
--------------------------------------------------------------------------------
1 | // generics2.rs
2 | //
3 | // This powerful wrapper provides the ability to store a positive integer value.
4 | // Rewrite it using generics so that it supports wrapping ANY type.
5 | //
6 | // Execute `rustlings hint generics2` or use the `hint` watch subcommand for a
7 | // hint.
8 |
9 | // I AM NOT DONE
10 |
11 | struct Wrapper {
12 | value: u32,
13 | }
14 |
15 | impl Wrapper {
16 | pub fn new(value: u32) -> Self {
17 | Wrapper { value }
18 | }
19 | }
20 |
21 | #[cfg(test)]
22 | mod tests {
23 | use super::*;
24 |
25 | #[test]
26 | fn store_u32_in_wrapper() {
27 | assert_eq!(Wrapper::new(42).value, 42);
28 | }
29 |
30 | #[test]
31 | fn store_str_in_wrapper() {
32 | assert_eq!(Wrapper::new("Foo").value, "Foo");
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/exercises/hashmaps/README.md:
--------------------------------------------------------------------------------
1 | # Hashmaps
2 |
3 | A *hash map* allows you to associate a value with a particular key.
4 | You may also know this by the names [*unordered map* in C++](https://en.cppreference.com/w/cpp/container/unordered_map),
5 | [*dictionary* in Python](https://docs.python.org/3/tutorial/datastructures.html#dictionaries) or an *associative array* in other languages.
6 |
7 | This is the other data structure that we've been talking about before, when
8 | talking about Vecs.
9 |
10 | ## Further information
11 |
12 | - [Storing Keys with Associated Values in Hash Maps](https://doc.rust-lang.org/book/ch08-03-hash-maps.html)
13 |
--------------------------------------------------------------------------------
/exercises/hashmaps/hashmaps1.rs:
--------------------------------------------------------------------------------
1 | // hashmaps1.rs
2 | //
3 | // A basket of fruits in the form of a hash map needs to be defined. The key
4 | // represents the name of the fruit and the value represents how many of that
5 | // particular fruit is in the basket. You have to put at least three different
6 | // types of fruits (e.g apple, banana, mango) in the basket and the total count
7 | // of all the fruits should be at least five.
8 | //
9 | // Make me compile and pass the tests!
10 | //
11 | // Execute `rustlings hint hashmaps1` or use the `hint` watch subcommand for a
12 | // hint.
13 |
14 | // I AM NOT DONE
15 |
16 | use std::collections::HashMap;
17 |
18 | fn fruit_basket() -> HashMap {
19 | let mut basket = // TODO: declare your hash map here.
20 |
21 | // Two bananas are already given for you :)
22 | basket.insert(String::from("banana"), 2);
23 |
24 | // TODO: Put more fruits in your basket here.
25 |
26 | basket
27 | }
28 |
29 | #[cfg(test)]
30 | mod tests {
31 | use super::*;
32 |
33 | #[test]
34 | fn at_least_three_types_of_fruits() {
35 | let basket = fruit_basket();
36 | assert!(basket.len() >= 3);
37 | }
38 |
39 | #[test]
40 | fn at_least_five_fruits() {
41 | let basket = fruit_basket();
42 | assert!(basket.values().sum::() >= 5);
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/exercises/hashmaps/hashmaps2.rs:
--------------------------------------------------------------------------------
1 | // hashmaps2.rs
2 | //
3 | // We're collecting different fruits to bake a delicious fruit cake. For this,
4 | // we have a basket, which we'll represent in the form of a hash map. The key
5 | // represents the name of each fruit we collect and the value represents how
6 | // many of that particular fruit we have collected. Three types of fruits -
7 | // Apple (4), Mango (2) and Lychee (5) are already in the basket hash map. You
8 | // must add fruit to the basket so that there is at least one of each kind and
9 | // more than 11 in total - we have a lot of mouths to feed. You are not allowed
10 | // to insert any more of these fruits!
11 | //
12 | // Make me pass the tests!
13 | //
14 | // Execute `rustlings hint hashmaps2` or use the `hint` watch subcommand for a
15 | // hint.
16 |
17 | // I AM NOT DONE
18 |
19 | use std::collections::HashMap;
20 |
21 | #[derive(Hash, PartialEq, Eq)]
22 | enum Fruit {
23 | Apple,
24 | Banana,
25 | Mango,
26 | Lychee,
27 | Pineapple,
28 | }
29 |
30 | fn fruit_basket(basket: &mut HashMap) {
31 | let fruit_kinds = vec![
32 | Fruit::Apple,
33 | Fruit::Banana,
34 | Fruit::Mango,
35 | Fruit::Lychee,
36 | Fruit::Pineapple,
37 | ];
38 |
39 | for fruit in fruit_kinds {
40 | // TODO: Insert new fruits if they are not already present in the
41 | // basket. Note that you are not allowed to put any type of fruit that's
42 | // already present!
43 | }
44 | }
45 |
46 | #[cfg(test)]
47 | mod tests {
48 | use super::*;
49 |
50 | // Don't modify this function!
51 | fn get_fruit_basket() -> HashMap {
52 | let mut basket = HashMap::::new();
53 | basket.insert(Fruit::Apple, 4);
54 | basket.insert(Fruit::Mango, 2);
55 | basket.insert(Fruit::Lychee, 5);
56 |
57 | basket
58 | }
59 |
60 | #[test]
61 | fn test_given_fruits_are_not_modified() {
62 | let mut basket = get_fruit_basket();
63 | fruit_basket(&mut basket);
64 | assert_eq!(*basket.get(&Fruit::Apple).unwrap(), 4);
65 | assert_eq!(*basket.get(&Fruit::Mango).unwrap(), 2);
66 | assert_eq!(*basket.get(&Fruit::Lychee).unwrap(), 5);
67 | }
68 |
69 | #[test]
70 | fn at_least_five_types_of_fruits() {
71 | let mut basket = get_fruit_basket();
72 | fruit_basket(&mut basket);
73 | let count_fruit_kinds = basket.len();
74 | assert!(count_fruit_kinds >= 5);
75 | }
76 |
77 | #[test]
78 | fn greater_than_eleven_fruits() {
79 | let mut basket = get_fruit_basket();
80 | fruit_basket(&mut basket);
81 | let count = basket.values().sum::();
82 | assert!(count > 11);
83 | }
84 |
85 | #[test]
86 | fn all_fruit_types_in_basket() {
87 | let mut basket = get_fruit_basket();
88 | fruit_basket(&mut basket);
89 | for amount in basket.values() {
90 | assert_ne!(amount, &0);
91 | }
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/exercises/hashmaps/hashmaps3.rs:
--------------------------------------------------------------------------------
1 | // hashmaps3.rs
2 | //
3 | // A list of scores (one per line) of a soccer match is given. Each line is of
4 | // the form : ",,,"
5 | // Example: England,France,4,2 (England scored 4 goals, France 2).
6 | //
7 | // You have to build a scores table containing the name of the team, goals the
8 | // team scored, and goals the team conceded. One approach to build the scores
9 | // table is to use a Hashmap. The solution is partially written to use a
10 | // Hashmap, complete it to pass the test.
11 | //
12 | // Make me pass the tests!
13 | //
14 | // Execute `rustlings hint hashmaps3` or use the `hint` watch subcommand for a
15 | // hint.
16 |
17 | // I AM NOT DONE
18 |
19 | use std::collections::HashMap;
20 |
21 | // A structure to store the goal details of a team.
22 | struct Team {
23 | goals_scored: u8,
24 | goals_conceded: u8,
25 | }
26 |
27 | fn build_scores_table(results: String) -> HashMap {
28 | // The name of the team is the key and its associated struct is the value.
29 | let mut scores: HashMap = HashMap::new();
30 |
31 | for r in results.lines() {
32 | let v: Vec<&str> = r.split(',').collect();
33 | let team_1_name = v[0].to_string();
34 | let team_1_score: u8 = v[2].parse().unwrap();
35 | let team_2_name = v[1].to_string();
36 | let team_2_score: u8 = v[3].parse().unwrap();
37 | // TODO: Populate the scores table with details extracted from the
38 | // current line. Keep in mind that goals scored by team_1
39 | // will be the number of goals conceded from team_2, and similarly
40 | // goals scored by team_2 will be the number of goals conceded by
41 | // team_1.
42 | }
43 | scores
44 | }
45 |
46 | #[cfg(test)]
47 | mod tests {
48 | use super::*;
49 |
50 | fn get_results() -> String {
51 | let results = "".to_string()
52 | + "England,France,4,2\n"
53 | + "France,Italy,3,1\n"
54 | + "Poland,Spain,2,0\n"
55 | + "Germany,England,2,1\n";
56 | results
57 | }
58 |
59 | #[test]
60 | fn build_scores() {
61 | let scores = build_scores_table(get_results());
62 |
63 | let mut keys: Vec<&String> = scores.keys().collect();
64 | keys.sort();
65 | assert_eq!(
66 | keys,
67 | vec!["England", "France", "Germany", "Italy", "Poland", "Spain"]
68 | );
69 | }
70 |
71 | #[test]
72 | fn validate_team_score_1() {
73 | let scores = build_scores_table(get_results());
74 | let team = scores.get("England").unwrap();
75 | assert_eq!(team.goals_scored, 5);
76 | assert_eq!(team.goals_conceded, 4);
77 | }
78 |
79 | #[test]
80 | fn validate_team_score_2() {
81 | let scores = build_scores_table(get_results());
82 | let team = scores.get("Spain").unwrap();
83 | assert_eq!(team.goals_scored, 0);
84 | assert_eq!(team.goals_conceded, 2);
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/exercises/iterators/README.md:
--------------------------------------------------------------------------------
1 | # Iterators
2 |
3 | This section will teach you about Iterators.
4 |
5 | ## Further information
6 |
7 | - [Iterator](https://doc.rust-lang.org/book/ch13-02-iterators.html)
8 | - [Iterator documentation](https://doc.rust-lang.org/stable/std/iter/)
9 |
--------------------------------------------------------------------------------
/exercises/iterators/iterators1.rs:
--------------------------------------------------------------------------------
1 | // iterators1.rs
2 | //
3 | // When performing operations on elements within a collection, iterators are
4 | // essential. This module helps you get familiar with the structure of using an
5 | // iterator and how to go through elements within an iterable collection.
6 | //
7 | // Make me compile by filling in the `???`s
8 | //
9 | // Execute `rustlings hint iterators1` or use the `hint` watch subcommand for a
10 | // hint.
11 |
12 | // I AM NOT DONE
13 |
14 | fn main() {
15 | let my_fav_fruits = vec!["banana", "custard apple", "avocado", "peach", "raspberry"];
16 |
17 | let mut my_iterable_fav_fruits = ???; // TODO: Step 1
18 |
19 | assert_eq!(my_iterable_fav_fruits.next(), Some(&"banana"));
20 | assert_eq!(my_iterable_fav_fruits.next(), ???); // TODO: Step 2
21 | assert_eq!(my_iterable_fav_fruits.next(), Some(&"avocado"));
22 | assert_eq!(my_iterable_fav_fruits.next(), ???); // TODO: Step 3
23 | assert_eq!(my_iterable_fav_fruits.next(), Some(&"raspberry"));
24 | assert_eq!(my_iterable_fav_fruits.next(), ???); // TODO: Step 4
25 | }
26 |
--------------------------------------------------------------------------------
/exercises/iterators/iterators2.rs:
--------------------------------------------------------------------------------
1 | // iterators2.rs
2 | //
3 | // In this exercise, you'll learn some of the unique advantages that iterators
4 | // can offer. Follow the steps to complete the exercise.
5 | //
6 | // Execute `rustlings hint iterators2` or use the `hint` watch subcommand for a
7 | // hint.
8 |
9 | // I AM NOT DONE
10 |
11 | // Step 1.
12 | // Complete the `capitalize_first` function.
13 | // "hello" -> "Hello"
14 | pub fn capitalize_first(input: &str) -> String {
15 | let mut c = input.chars();
16 | match c.next() {
17 | None => String::new(),
18 | Some(first) => ???,
19 | }
20 | }
21 |
22 | // Step 2.
23 | // Apply the `capitalize_first` function to a slice of string slices.
24 | // Return a vector of strings.
25 | // ["hello", "world"] -> ["Hello", "World"]
26 | pub fn capitalize_words_vector(words: &[&str]) -> Vec {
27 | vec![]
28 | }
29 |
30 | // Step 3.
31 | // Apply the `capitalize_first` function again to a slice of string slices.
32 | // Return a single string.
33 | // ["hello", " ", "world"] -> "Hello World"
34 | pub fn capitalize_words_string(words: &[&str]) -> String {
35 | String::new()
36 | }
37 |
38 | #[cfg(test)]
39 | mod tests {
40 | use super::*;
41 |
42 | #[test]
43 | fn test_success() {
44 | assert_eq!(capitalize_first("hello"), "Hello");
45 | }
46 |
47 | #[test]
48 | fn test_empty() {
49 | assert_eq!(capitalize_first(""), "");
50 | }
51 |
52 | #[test]
53 | fn test_iterate_string_vec() {
54 | let words = vec!["hello", "world"];
55 | assert_eq!(capitalize_words_vector(&words), ["Hello", "World"]);
56 | }
57 |
58 | #[test]
59 | fn test_iterate_into_string() {
60 | let words = vec!["hello", " ", "world"];
61 | assert_eq!(capitalize_words_string(&words), "Hello World");
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/exercises/lifetimes/README.md:
--------------------------------------------------------------------------------
1 | # Lifetimes
2 |
3 | Lifetimes tell the compiler how to check whether references live long
4 | enough to be valid in any given situation. For example lifetimes say
5 | "make sure parameter 'a' lives as long as parameter 'b' so that the return
6 | value is valid".
7 |
8 | They are only necessary on borrows, i.e. references,
9 | since copied parameters or moves are owned in their scope and cannot
10 | be referenced outside. Lifetimes mean that calling code of e.g. functions
11 | can be checked to make sure their arguments are valid. Lifetimes are
12 | restrictive of their callers.
13 |
14 | If you'd like to learn more about lifetime annotations, the
15 | [lifetimekata](https://tfpk.github.io/lifetimekata/) project
16 | has a similar style of exercises to Rustlings, but is all about
17 | learning to write lifetime annotations.
18 |
19 | ## Further information
20 |
21 | - [Lifetimes (in Rust By Example)](https://doc.rust-lang.org/stable/rust-by-example/scope/lifetime.html)
22 | - [Validating References with Lifetimes](https://doc.rust-lang.org/book/ch10-03-lifetime-syntax.html)
23 |
--------------------------------------------------------------------------------
/exercises/lifetimes/lifetimes1.rs:
--------------------------------------------------------------------------------
1 | // lifetimes1.rs
2 | //
3 | // The Rust compiler needs to know how to check whether supplied references are
4 | // valid, so that it can let the programmer know if a reference is at risk of
5 | // going out of scope before it is used. Remember, references are borrows and do
6 | // not own their own data. What if their owner goes out of scope?
7 | //
8 | // Execute `rustlings hint lifetimes1` or use the `hint` watch subcommand for a
9 | // hint.
10 |
11 | // I AM NOT DONE
12 |
13 | fn longest(x: &str, y: &str) -> &str {
14 | if x.len() > y.len() {
15 | x
16 | } else {
17 | y
18 | }
19 | }
20 |
21 | fn main() {
22 | let string1 = String::from("abcd");
23 | let string2 = "xyz";
24 |
25 | let result = longest(string1.as_str(), string2);
26 | println!("The longest string is '{}'", result);
27 | }
28 |
--------------------------------------------------------------------------------
/exercises/lifetimes/lifetimes2.rs:
--------------------------------------------------------------------------------
1 | // lifetimes2.rs
2 | //
3 | // So if the compiler is just validating the references passed to the annotated
4 | // parameters and the return type, what do we need to change?
5 | //
6 | // Execute `rustlings hint lifetimes2` or use the `hint` watch subcommand for a
7 | // hint.
8 |
9 | // I AM NOT DONE
10 |
11 | fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
12 | if x.len() > y.len() {
13 | x
14 | } else {
15 | y
16 | }
17 | }
18 |
19 | fn main() {
20 | let string1 = String::from("long string is long");
21 | let result;
22 | {
23 | let string2 = String::from("xyz");
24 | result = longest(string1.as_str(), string2.as_str());
25 | }
26 | println!("The longest string is '{}'", result);
27 | }
28 |
--------------------------------------------------------------------------------
/exercises/lifetimes/lifetimes3.rs:
--------------------------------------------------------------------------------
1 | // lifetimes3.rs
2 | //
3 | // Lifetimes are also needed when structs hold references.
4 | //
5 | // Execute `rustlings hint lifetimes3` or use the `hint` watch subcommand for a
6 | // hint.
7 |
8 | // I AM NOT DONE
9 |
10 | struct Book {
11 | author: &str,
12 | title: &str,
13 | }
14 |
15 | fn main() {
16 | let name = String::from("Jill Smith");
17 | let title = String::from("Fish Flying");
18 | let book = Book { author: &name, title: &title };
19 |
20 | println!("{} by {}", book.title, book.author);
21 | }
22 |
--------------------------------------------------------------------------------
/exercises/macros/README.md:
--------------------------------------------------------------------------------
1 | # Macros
2 |
3 | Rust's macro system is very powerful, but also kind of difficult to wrap your
4 | head around. We're not going to teach you how to write your own fully-featured
5 | macros. Instead, we'll show you how to use and create them.
6 |
7 | If you'd like to learn more about writing your own macros, the
8 | [macrokata](https://github.com/tfpk/macrokata) project has a similar style
9 | of exercises to Rustlings, but is all about learning to write Macros.
10 |
11 | ## Further information
12 |
13 | - [Macros](https://doc.rust-lang.org/book/ch19-06-macros.html)
14 | - [The Little Book of Rust Macros](https://veykril.github.io/tlborm/)
15 |
--------------------------------------------------------------------------------
/exercises/macros/macros1.rs:
--------------------------------------------------------------------------------
1 | // macros1.rs
2 | //
3 | // Execute `rustlings hint macros1` or use the `hint` watch subcommand for a
4 | // hint.
5 |
6 | // I AM NOT DONE
7 |
8 | macro_rules! my_macro {
9 | () => {
10 | println!("Check out my macro!");
11 | };
12 | }
13 |
14 | fn main() {
15 | my_macro();
16 | }
17 |
--------------------------------------------------------------------------------
/exercises/macros/macros2.rs:
--------------------------------------------------------------------------------
1 | // macros2.rs
2 | //
3 | // Execute `rustlings hint macros2` or use the `hint` watch subcommand for a
4 | // hint.
5 |
6 | // I AM NOT DONE
7 |
8 | fn main() {
9 | my_macro!();
10 | }
11 |
12 | macro_rules! my_macro {
13 | () => {
14 | println!("Check out my macro!");
15 | };
16 | }
17 |
--------------------------------------------------------------------------------
/exercises/modules/README.md:
--------------------------------------------------------------------------------
1 | # Modules
2 |
3 | In this section we'll give you an introduction to Rust's module system.
4 |
5 | ## Further information
6 |
7 | - [The Module System](https://doc.rust-lang.org/book/ch07-00-managing-growing-projects-with-packages-crates-and-modules.html)
8 |
--------------------------------------------------------------------------------
/exercises/modules/modules2.rs:
--------------------------------------------------------------------------------
1 | // modules2.rs
2 | //
3 | // You can bring module paths into scopes and provide new names for them with
4 | // the 'use' and 'as' keywords. Fix these 'use' statements to make the code
5 | // compile.
6 | //
7 | // Execute `rustlings hint modules2` or use the `hint` watch subcommand for a
8 | // hint.
9 |
10 | // I AM NOT DONE
11 |
12 | mod delicious_snacks {
13 | // TODO: Fix these use statements
14 | use self::fruits::PEAR as ???
15 | use self::veggies::CUCUMBER as ???
16 |
17 | mod fruits {
18 | pub const PEAR: &'static str = "Pear";
19 | pub const APPLE: &'static str = "Apple";
20 | }
21 |
22 | mod veggies {
23 | pub const CUCUMBER: &'static str = "Cucumber";
24 | pub const CARROT: &'static str = "Carrot";
25 | }
26 | }
27 |
28 | fn main() {
29 | println!(
30 | "favorite snacks: {} and {}",
31 | delicious_snacks::fruit,
32 | delicious_snacks::veggie
33 | );
34 | }
35 |
--------------------------------------------------------------------------------
/exercises/move_semantics/README.md:
--------------------------------------------------------------------------------
1 | # Move Semantics
2 |
3 | These exercises are adapted from [pnkfelix](https://github.com/pnkfelix)'s [Rust Tutorial](https://pnkfelix.github.io/rust-examples-icfp2014/) -- Thank you Felix!!!
4 |
5 | ## Further information
6 |
7 | For this section, the book links are especially important.
8 |
9 | - [Ownership](https://doc.rust-lang.org/book/ch04-01-what-is-ownership.html)
10 | - [Reference and borrowing](https://doc.rust-lang.org/book/ch04-02-references-and-borrowing.html)
11 |
--------------------------------------------------------------------------------
/exercises/move_semantics/move_semantics4.rs:
--------------------------------------------------------------------------------
1 | // move_semantics4.rs
2 | //
3 | // Refactor this code so that instead of passing `vec0` into the `fill_vec`
4 | // function, the Vector gets created in the function itself and passed back to
5 | // the main function.
6 | //
7 | // Execute `rustlings hint move_semantics4` or use the `hint` watch subcommand
8 | // for a hint.
9 |
10 | // I AM NOT DONE
11 |
12 | fn main() {
13 | let vec0 = Vec::new();
14 |
15 | let mut vec1 = fill_vec(vec0);
16 |
17 | println!("{} has length {} content `{:?}`", "vec1", vec1.len(), vec1);
18 |
19 | vec1.push(88);
20 |
21 | println!("{} has length {} content `{:?}`", "vec1", vec1.len(), vec1);
22 | }
23 |
24 | // `fill_vec()` no longer takes `vec: Vec` as argument
25 | fn fill_vec() -> Vec {
26 | let mut vec = vec;
27 |
28 | vec.push(22);
29 | vec.push(44);
30 | vec.push(66);
31 |
32 | vec
33 | }
--------------------------------------------------------------------------------
/exercises/move_semantics/move_semantics5.rs:
--------------------------------------------------------------------------------
1 | // move_semantics5.rs
2 | //
3 | // Make me compile only by reordering the lines in `main()`, but without adding,
4 | // changing or removing any of them.
5 | //
6 | // Execute `rustlings hint move_semantics5` or use the `hint` watch subcommand
7 | // for a hint.
8 |
9 | // I AM NOT DONE
10 |
11 | fn main() {
12 | let mut x = 100;
13 | let y = &mut x;
14 | let z = &mut x;
15 | *y += 100;
16 | *z += 1000;
17 | assert_eq!(x, 1200);
18 | }
--------------------------------------------------------------------------------
/exercises/move_semantics/move_semantics6.rs:
--------------------------------------------------------------------------------
1 | // move_semantics6.rs
2 | //
3 | // You can't change anything except adding or removing references.
4 | //
5 | // Execute `rustlings hint move_semantics6` or use the `hint` watch subcommand
6 | // for a hint.
7 |
8 | // I AM NOT DONE
9 |
10 | fn main() {
11 | let data = "Rust is great!".to_string();
12 |
13 | get_char(data);
14 |
15 | string_uppercase(&data);
16 | }
17 |
18 | // Should not take ownership
19 | fn get_char(data: String) -> char {
20 | data.chars().last().unwrap()
21 | }
22 |
23 | // Should take ownership
24 | fn string_uppercase(mut data: &String) {
25 | data = &data.to_uppercase();
26 |
27 | println!("{}", data);
28 | }
--------------------------------------------------------------------------------
/exercises/options/README.md:
--------------------------------------------------------------------------------
1 | # Options
2 |
3 | Type Option represents an optional value: every Option is either Some and contains a value, or None, and does not.
4 | Option types are very common in Rust code, as they have a number of uses:
5 |
6 | - Initial values
7 | - Return values for functions that are not defined over their entire input range (partial functions)
8 | - Return value for otherwise reporting simple errors, where None is returned on error
9 | - Optional struct fields
10 | - Struct fields that can be loaned or "taken"
11 | - Optional function arguments
12 | - Nullable pointers
13 | - Swapping things out of difficult situations
14 |
15 | ## Further Information
16 |
17 | - [Option Enum Format](https://doc.rust-lang.org/stable/book/ch10-01-syntax.html#in-enum-definitions)
18 | - [Option Module Documentation](https://doc.rust-lang.org/std/option/)
19 | - [Option Enum Documentation](https://doc.rust-lang.org/std/option/enum.Option.html)
20 | - [if let](https://doc.rust-lang.org/rust-by-example/flow_control/if_let.html)
21 | - [while let](https://doc.rust-lang.org/rust-by-example/flow_control/while_let.html)
22 |
--------------------------------------------------------------------------------
/exercises/options/options1.rs:
--------------------------------------------------------------------------------
1 | // options1.rs
2 | //
3 | // Execute `rustlings hint options1` or use the `hint` watch subcommand for a
4 | // hint.
5 |
6 | // I AM NOT DONE
7 |
8 | // This function returns how much icecream there is left in the fridge.
9 | // If it's before 10PM, there's 5 pieces left. At 10PM, someone eats them
10 | // all, so there'll be no more left :(
11 | fn maybe_icecream(time_of_day: u16) -> Option {
12 | // We use the 24-hour system here, so 10PM is a value of 22 and 12AM is a
13 | // value of 0 The Option output should gracefully handle cases where
14 | // time_of_day > 23.
15 | // TODO: Complete the function body - remember to return an Option!
16 | ???
17 | }
18 |
19 | #[cfg(test)]
20 | mod tests {
21 | use super::*;
22 |
23 | #[test]
24 | fn check_icecream() {
25 | assert_eq!(maybe_icecream(9), Some(5));
26 | assert_eq!(maybe_icecream(10), Some(5));
27 | assert_eq!(maybe_icecream(23), Some(0));
28 | assert_eq!(maybe_icecream(22), Some(0));
29 | assert_eq!(maybe_icecream(25), None);
30 | }
31 |
32 | #[test]
33 | fn raw_value() {
34 | // TODO: Fix this test. How do you get at the value contained in the
35 | // Option?
36 | let icecreams = maybe_icecream(12);
37 | assert_eq!(icecreams, 5);
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/exercises/quiz2.rs:
--------------------------------------------------------------------------------
1 | // quiz2.rs
2 | //
3 | // This is a quiz for the following sections:
4 | // - Strings
5 | // - Vecs
6 | // - Move semantics
7 | // - Modules
8 | // - Enums
9 | //
10 | // Let's build a little machine in the form of a function. As input, we're going
11 | // to give a list of strings and commands. These commands determine what action
12 | // is going to be applied to the string. It can either be:
13 | // - Uppercase the string
14 | // - Trim the string
15 | // - Append "bar" to the string a specified amount of times
16 | // The exact form of this will be:
17 | // - The input is going to be a Vector of a 2-length tuple,
18 | // the first element is the string, the second one is the command.
19 | // - The output element is going to be a Vector of strings.
20 | //
21 | // No hints this time!
22 |
23 | // I AM NOT DONE
24 |
25 | pub enum Command {
26 | Uppercase,
27 | Trim,
28 | Append(usize),
29 | }
30 |
31 | mod my_module {
32 | use super::Command;
33 |
34 | // TODO: Complete the function signature!
35 | pub fn transformer(input: ???) -> ??? {
36 | // TODO: Complete the output declaration!
37 | let mut output: ??? = vec![];
38 | for (string, command) in input.iter() {
39 | // TODO: Complete the function body. You can do it!
40 | }
41 | output
42 | }
43 | }
44 |
45 | #[cfg(test)]
46 | mod tests {
47 | // TODO: What do we need to import to have `transformer` in scope?
48 | use ???;
49 | use super::Command;
50 |
51 | #[test]
52 | fn it_works() {
53 | let output = transformer(vec![
54 | ("hello".into(), Command::Uppercase),
55 | (" all roads lead to rome! ".into(), Command::Trim),
56 | ("foo".into(), Command::Append(1)),
57 | ("bar".into(), Command::Append(5)),
58 | ]);
59 | assert_eq!(output[0], "HELLO");
60 | assert_eq!(output[1], "all roads lead to rome!");
61 | assert_eq!(output[2], "foobar");
62 | assert_eq!(output[3], "barbarbarbarbarbar");
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/exercises/quiz3.rs:
--------------------------------------------------------------------------------
1 | // quiz3.rs
2 | //
3 | // This quiz tests:
4 | // - Generics
5 | // - Traits
6 | //
7 | // An imaginary magical school has a new report card generation system written
8 | // in Rust! Currently the system only supports creating report cards where the
9 | // student's grade is represented numerically (e.g. 1.0 -> 5.5). However, the
10 | // school also issues alphabetical grades (A+ -> F-) and needs to be able to
11 | // print both types of report card!
12 | //
13 | // Make the necessary code changes in the struct ReportCard and the impl block
14 | // to support alphabetical report cards. Change the Grade in the second test to
15 | // "A+" to show that your changes allow alphabetical grades.
16 | //
17 | // Execute `rustlings hint quiz3` or use the `hint` watch subcommand for a hint.
18 |
19 | // I AM NOT DONE
20 |
21 | pub struct ReportCard {
22 | pub grade: f32,
23 | pub student_name: String,
24 | pub student_age: u8,
25 | }
26 |
27 | impl ReportCard {
28 | pub fn print(&self) -> String {
29 | format!("{} ({}) - achieved a grade of {}",
30 | &self.student_name, &self.student_age, &self.grade)
31 | }
32 | }
33 |
34 | #[cfg(test)]
35 | mod tests {
36 | use super::*;
37 |
38 | #[test]
39 | fn generate_numeric_report_card() {
40 | let report_card = ReportCard {
41 | grade: 2.1,
42 | student_name: "Tom Wriggle".to_string(),
43 | student_age: 12,
44 | };
45 | assert_eq!(
46 | report_card.print(),
47 | "Tom Wriggle (12) - achieved a grade of 2.1"
48 | );
49 | }
50 |
51 | #[test]
52 | fn generate_alphabetic_report_card() {
53 | // TODO: Make sure to change the grade here after you finish the exercise.
54 | let report_card = ReportCard {
55 | grade: 2.1,
56 | student_name: "Gary Plotter".to_string(),
57 | student_age: 11,
58 | };
59 | assert_eq!(
60 | report_card.print(),
61 | "Gary Plotter (11) - achieved a grade of A+"
62 | );
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/exercises/smart_pointers/README.md:
--------------------------------------------------------------------------------
1 | # Smart Pointers
2 |
3 | In Rust, smart pointers are variables that contain an address in memory and reference some other data, but they also have additional metadata and capabilities.
4 | Smart pointers in Rust often own the data they point to, while references only borrow data.
5 |
6 | ## Further Information
7 |
8 | - [Smart Pointers](https://doc.rust-lang.org/book/ch15-00-smart-pointers.html)
9 | - [Using Box to Point to Data on the Heap](https://doc.rust-lang.org/book/ch15-01-box.html)
10 | - [Rc\, the Reference Counted Smart Pointer](https://doc.rust-lang.org/book/ch15-04-rc.html)
11 | - [Shared-State Concurrency](https://doc.rust-lang.org/book/ch16-03-shared-state.html)
12 | - [Cow Documentation](https://doc.rust-lang.org/std/borrow/enum.Cow.html)
13 |
--------------------------------------------------------------------------------
/exercises/smart_pointers/arc1.rs:
--------------------------------------------------------------------------------
1 | // arc1.rs
2 | //
3 | // In this exercise, we are given a Vec of u32 called "numbers" with values
4 | // ranging from 0 to 99 -- [ 0, 1, 2, ..., 98, 99 ] We would like to use this
5 | // set of numbers within 8 different threads simultaneously. Each thread is
6 | // going to get the sum of every eighth value, with an offset.
7 | //
8 | // The first thread (offset 0), will sum 0, 8, 16, ...
9 | // The second thread (offset 1), will sum 1, 9, 17, ...
10 | // The third thread (offset 2), will sum 2, 10, 18, ...
11 | // ...
12 | // The eighth thread (offset 7), will sum 7, 15, 23, ...
13 | //
14 | // Because we are using threads, our values need to be thread-safe. Therefore,
15 | // we are using Arc. We need to make a change in each of the two TODOs.
16 | //
17 | // Make this code compile by filling in a value for `shared_numbers` where the
18 | // first TODO comment is, and create an initial binding for `child_numbers`
19 | // where the second TODO comment is. Try not to create any copies of the
20 | // `numbers` Vec!
21 | //
22 | // Execute `rustlings hint arc1` or use the `hint` watch subcommand for a hint.
23 |
24 | // I AM NOT DONE
25 |
26 | #![forbid(unused_imports)] // Do not change this, (or the next) line.
27 | use std::sync::Arc;
28 | use std::thread;
29 |
30 | fn main() {
31 | let numbers: Vec<_> = (0..100u32).collect();
32 | let shared_numbers = // TODO
33 | let mut joinhandles = Vec::new();
34 |
35 | for offset in 0..8 {
36 | let child_numbers = // TODO
37 | joinhandles.push(thread::spawn(move || {
38 | let sum: u32 = child_numbers.iter().filter(|&&n| n % 8 == offset).sum();
39 | println!("Sum of offset {} is {}", offset, sum);
40 | }));
41 | }
42 | for handle in joinhandles.into_iter() {
43 | handle.join().unwrap();
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/exercises/smart_pointers/box1.rs:
--------------------------------------------------------------------------------
1 | // box1.rs
2 | //
3 | // At compile time, Rust needs to know how much space a type takes up. This
4 | // becomes problematic for recursive types, where a value can have as part of
5 | // itself another value of the same type. To get around the issue, we can use a
6 | // `Box` - a smart pointer used to store data on the heap, which also allows us
7 | // to wrap a recursive type.
8 | //
9 | // The recursive type we're implementing in this exercise is the `cons list` - a
10 | // data structure frequently found in functional programming languages. Each
11 | // item in a cons list contains two elements: the value of the current item and
12 | // the next item. The last item is a value called `Nil`.
13 | //
14 | // Step 1: use a `Box` in the enum definition to make the code compile
15 | // Step 2: create both empty and non-empty cons lists by replacing `todo!()`
16 | //
17 | // Note: the tests should not be changed
18 | //
19 | // Execute `rustlings hint box1` or use the `hint` watch subcommand for a hint.
20 |
21 | // I AM NOT DONE
22 |
23 | #[derive(PartialEq, Debug)]
24 | pub enum List {
25 | Cons(i32, List),
26 | Nil,
27 | }
28 |
29 | fn main() {
30 | println!("This is an empty cons list: {:?}", create_empty_list());
31 | println!(
32 | "This is a non-empty cons list: {:?}",
33 | create_non_empty_list()
34 | );
35 | }
36 |
37 | pub fn create_empty_list() -> List {
38 | todo!()
39 | }
40 |
41 | pub fn create_non_empty_list() -> List {
42 | todo!()
43 | }
44 |
45 | #[cfg(test)]
46 | mod tests {
47 | use super::*;
48 |
49 | #[test]
50 | fn test_create_empty_list() {
51 | assert_eq!(List::Nil, create_empty_list())
52 | }
53 |
54 | #[test]
55 | fn test_create_non_empty_list() {
56 | assert_ne!(create_empty_list(), create_non_empty_list())
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/exercises/smart_pointers/cow1.rs:
--------------------------------------------------------------------------------
1 | // cow1.rs
2 | //
3 | // This exercise explores the Cow, or Clone-On-Write type. Cow is a
4 | // clone-on-write smart pointer. It can enclose and provide immutable access to
5 | // borrowed data, and clone the data lazily when mutation or ownership is
6 | // required. The type is designed to work with general borrowed data via the
7 | // Borrow trait.
8 | //
9 | // This exercise is meant to show you what to expect when passing data to Cow.
10 | // Fix the unit tests by checking for Cow::Owned(_) and Cow::Borrowed(_) at the
11 | // TODO markers.
12 | //
13 | // Execute `rustlings hint cow1` or use the `hint` watch subcommand for a hint.
14 |
15 | // I AM NOT DONE
16 |
17 | use std::borrow::Cow;
18 |
19 | fn abs_all<'a, 'b>(input: &'a mut Cow<'b, [i32]>) -> &'a mut Cow<'b, [i32]> {
20 | for i in 0..input.len() {
21 | let v = input[i];
22 | if v < 0 {
23 | // Clones into a vector if not already owned.
24 | input.to_mut()[i] = -v;
25 | }
26 | }
27 | input
28 | }
29 |
30 | #[cfg(test)]
31 | mod tests {
32 | use super::*;
33 |
34 | #[test]
35 | fn reference_mutation() -> Result<(), &'static str> {
36 | // Clone occurs because `input` needs to be mutated.
37 | let slice = [-1, 0, 1];
38 | let mut input = Cow::from(&slice[..]);
39 | match abs_all(&mut input) {
40 | Cow::Owned(_) => Ok(()),
41 | _ => Err("Expected owned value"),
42 | }
43 | }
44 |
45 | #[test]
46 | fn reference_no_mutation() -> Result<(), &'static str> {
47 | // No clone occurs because `input` doesn't need to be mutated.
48 | let slice = [0, 1, 2];
49 | let mut input = Cow::from(&slice[..]);
50 | match abs_all(&mut input) {
51 | // TODO
52 | }
53 | }
54 |
55 | #[test]
56 | fn owned_no_mutation() -> Result<(), &'static str> {
57 | // We can also pass `slice` without `&` so Cow owns it directly. In this
58 | // case no mutation occurs and thus also no clone, but the result is
59 | // still owned because it was never borrowed or mutated.
60 | let slice = vec![0, 1, 2];
61 | let mut input = Cow::from(slice);
62 | match abs_all(&mut input) {
63 | // TODO
64 | }
65 | }
66 |
67 | #[test]
68 | fn owned_mutation() -> Result<(), &'static str> {
69 | // Of course this is also the case if a mutation does occur. In this
70 | // case the call to `to_mut()` returns a reference to the same data as
71 | // before.
72 | let slice = vec![-1, 0, 1];
73 | let mut input = Cow::from(slice);
74 | match abs_all(&mut input) {
75 | // TODO
76 | }
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/exercises/smart_pointers/rc1.rs:
--------------------------------------------------------------------------------
1 | // rc1.rs
2 | //
3 | // In this exercise, we want to express the concept of multiple owners via the
4 | // Rc type. This is a model of our solar system - there is a Sun type and
5 | // multiple Planets. The Planets take ownership of the sun, indicating that they
6 | // revolve around the sun.
7 | //
8 | // Make this code compile by using the proper Rc primitives to express that the
9 | // sun has multiple owners.
10 | //
11 | // Execute `rustlings hint rc1` or use the `hint` watch subcommand for a hint.
12 |
13 | // I AM NOT DONE
14 |
15 | use std::rc::Rc;
16 |
17 | #[derive(Debug)]
18 | struct Sun {}
19 |
20 | #[derive(Debug)]
21 | enum Planet {
22 | Mercury(Rc),
23 | Venus(Rc),
24 | Earth(Rc),
25 | Mars(Rc),
26 | Jupiter(Rc),
27 | Saturn(Rc),
28 | Uranus(Rc),
29 | Neptune(Rc),
30 | }
31 |
32 | impl Planet {
33 | fn details(&self) {
34 | println!("Hi from {:?}!", self)
35 | }
36 | }
37 |
38 | fn main() {
39 | let sun = Rc::new(Sun {});
40 | println!("reference count = {}", Rc::strong_count(&sun)); // 1 reference
41 |
42 | let mercury = Planet::Mercury(Rc::clone(&sun));
43 | println!("reference count = {}", Rc::strong_count(&sun)); // 2 references
44 | mercury.details();
45 |
46 | let venus = Planet::Venus(Rc::clone(&sun));
47 | println!("reference count = {}", Rc::strong_count(&sun)); // 3 references
48 | venus.details();
49 |
50 | let earth = Planet::Earth(Rc::clone(&sun));
51 | println!("reference count = {}", Rc::strong_count(&sun)); // 4 references
52 | earth.details();
53 |
54 | let mars = Planet::Mars(Rc::clone(&sun));
55 | println!("reference count = {}", Rc::strong_count(&sun)); // 5 references
56 | mars.details();
57 |
58 | let jupiter = Planet::Jupiter(Rc::clone(&sun));
59 | println!("reference count = {}", Rc::strong_count(&sun)); // 6 references
60 | jupiter.details();
61 |
62 | // TODO
63 | let saturn = Planet::Saturn(Rc::new(Sun {}));
64 | println!("reference count = {}", Rc::strong_count(&sun)); // 7 references
65 | saturn.details();
66 |
67 | // TODO
68 | let uranus = Planet::Uranus(Rc::new(Sun {}));
69 | println!("reference count = {}", Rc::strong_count(&sun)); // 8 references
70 | uranus.details();
71 |
72 | // TODO
73 | let neptune = Planet::Neptune(Rc::new(Sun {}));
74 | println!("reference count = {}", Rc::strong_count(&sun)); // 9 references
75 | neptune.details();
76 |
77 | assert_eq!(Rc::strong_count(&sun), 9);
78 |
79 | drop(neptune);
80 | println!("reference count = {}", Rc::strong_count(&sun)); // 8 references
81 |
82 | drop(uranus);
83 | println!("reference count = {}", Rc::strong_count(&sun)); // 7 references
84 |
85 | drop(saturn);
86 | println!("reference count = {}", Rc::strong_count(&sun)); // 6 references
87 |
88 | drop(jupiter);
89 | println!("reference count = {}", Rc::strong_count(&sun)); // 5 references
90 |
91 | drop(mars);
92 | println!("reference count = {}", Rc::strong_count(&sun)); // 4 references
93 |
94 | // TODO
95 | println!("reference count = {}", Rc::strong_count(&sun)); // 3 references
96 |
97 | // TODO
98 | println!("reference count = {}", Rc::strong_count(&sun)); // 2 references
99 |
100 | // TODO
101 | println!("reference count = {}", Rc::strong_count(&sun)); // 1 reference
102 |
103 | assert_eq!(Rc::strong_count(&sun), 1);
104 | }
105 |
--------------------------------------------------------------------------------
/exercises/strings/README.md:
--------------------------------------------------------------------------------
1 | # Strings
2 |
3 | Rust has two string types, a string slice (`&str`) and an owned string (`String`).
4 | We're not going to dictate when you should use which one, but we'll show you how
5 | to identify and create them, as well as use them.
6 |
7 | ## Further information
8 |
9 | - [Strings](https://doc.rust-lang.org/book/ch08-02-strings.html)
10 |
--------------------------------------------------------------------------------
/exercises/strings/strings3.rs:
--------------------------------------------------------------------------------
1 | // strings3.rs
2 | //
3 | // Execute `rustlings hint strings3` or use the `hint` watch subcommand for a
4 | // hint.
5 |
6 | // I AM NOT DONE
7 |
8 | fn trim_me(input: &str) -> String {
9 | // TODO: Remove whitespace from both ends of a string!
10 | ???
11 | }
12 |
13 | fn compose_me(input: &str) -> String {
14 | // TODO: Add " world!" to the string! There's multiple ways to do this!
15 | ???
16 | }
17 |
18 | fn replace_me(input: &str) -> String {
19 | // TODO: Replace "cars" in the string with "balloons"!
20 | ???
21 | }
22 |
23 | #[cfg(test)]
24 | mod tests {
25 | use super::*;
26 |
27 | #[test]
28 | fn trim_a_string() {
29 | assert_eq!(trim_me("Hello! "), "Hello!");
30 | assert_eq!(trim_me(" What's up!"), "What's up!");
31 | assert_eq!(trim_me(" Hola! "), "Hola!");
32 | }
33 |
34 | #[test]
35 | fn compose_a_string() {
36 | assert_eq!(compose_me("Hello"), "Hello world!");
37 | assert_eq!(compose_me("Goodbye"), "Goodbye world!");
38 | }
39 |
40 | #[test]
41 | fn replace_a_string() {
42 | assert_eq!(replace_me("I think cars are cool"), "I think balloons are cool");
43 | assert_eq!(replace_me("I love to look at cars"), "I love to look at balloons");
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/exercises/strings/strings4.rs:
--------------------------------------------------------------------------------
1 | // strings4.rs
2 | //
3 | // Ok, here are a bunch of values-- some are `String`s, some are `&str`s. Your
4 | // task is to call one of these two functions on each value depending on what
5 | // you think each value is. That is, add either `string_slice` or `string`
6 | // before the parentheses on each line. If you're right, it will compile!
7 | //
8 | // No hints this time!
9 |
10 | // I AM NOT DONE
11 |
12 | fn string_slice(arg: &str) {
13 | println!("{}", arg);
14 | }
15 | fn string(arg: String) {
16 | println!("{}", arg);
17 | }
18 |
19 | fn main() {
20 | ???("blue");
21 | ???("red".to_string());
22 | ???(String::from("hi"));
23 | ???("rust is fun!".to_owned());
24 | ???("nice weather".into());
25 | ???(format!("Interpolation {}", "Station"));
26 | ???(&String::from("abc")[0..1]);
27 | ???(" hello there ".trim());
28 | ???("Happy Monday!".to_string().replace("Mon", "Tues"));
29 | ???("mY sHiFt KeY iS sTiCkY".to_lowercase());
30 | }
31 |
--------------------------------------------------------------------------------
/exercises/structs/README.md:
--------------------------------------------------------------------------------
1 | # Structs
2 |
3 | Rust has three struct types: a classic C struct, a tuple struct, and a unit struct.
4 |
5 | ## Further information
6 |
7 | - [Structures](https://doc.rust-lang.org/book/ch05-01-defining-structs.html)
8 | - [Method Syntax](https://doc.rust-lang.org/book/ch05-03-method-syntax.html)
9 |
--------------------------------------------------------------------------------
/exercises/structs/structs1.rs:
--------------------------------------------------------------------------------
1 | // structs1.rs
2 | //
3 | // Address all the TODOs to make the tests pass!
4 | //
5 | // Execute `rustlings hint structs1` or use the `hint` watch subcommand for a
6 | // hint.
7 |
8 | // I AM NOT DONE
9 |
10 | struct ColorClassicStruct {
11 | // TODO: Something goes here
12 | }
13 |
14 | struct ColorTupleStruct(/* TODO: Something goes here */);
15 |
16 | #[derive(Debug)]
17 | struct UnitLikeStruct;
18 |
19 | #[cfg(test)]
20 | mod tests {
21 | use super::*;
22 |
23 | #[test]
24 | fn classic_c_structs() {
25 | // TODO: Instantiate a classic c struct!
26 | // let green =
27 |
28 | assert_eq!(green.red, 0);
29 | assert_eq!(green.green, 255);
30 | assert_eq!(green.blue, 0);
31 | }
32 |
33 | #[test]
34 | fn tuple_structs() {
35 | // TODO: Instantiate a tuple struct!
36 | // let green =
37 |
38 | assert_eq!(green.0, 0);
39 | assert_eq!(green.1, 255);
40 | assert_eq!(green.2, 0);
41 | }
42 |
43 | #[test]
44 | fn unit_structs() {
45 | // TODO: Instantiate a unit-like struct!
46 | // let unit_like_struct =
47 | let message = format!("{:?}s are fun!", unit_like_struct);
48 |
49 | assert_eq!(message, "UnitLikeStructs are fun!");
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/exercises/traits/README.md:
--------------------------------------------------------------------------------
1 | # Traits
2 |
3 | A trait is a collection of methods.
4 |
5 | Data types can implement traits. To do so, the methods making up the trait are defined for the data type. For example, the `String` data type implements the `From<&str>` trait. This allows a user to write `String::from("hello")`.
6 |
7 | In this way, traits are somewhat similar to Java interfaces and C++ abstract classes.
8 |
9 | Some additional common Rust traits include:
10 |
11 | - `Clone` (the `clone` method)
12 | - `Display` (which allows formatted display via `{}`)
13 | - `Debug` (which allows formatted display via `{:?}`)
14 |
15 | Because traits indicate shared behavior between data types, they are useful when writing generics.
16 |
17 | ## Further information
18 |
19 | - [Traits](https://doc.rust-lang.org/book/ch10-02-traits.html)
20 |
--------------------------------------------------------------------------------
/exercises/traits/traits3.rs:
--------------------------------------------------------------------------------
1 | // traits3.rs
2 | //
3 | // Your task is to implement the Licensed trait for both structures and have
4 | // them return the same information without writing the same function twice.
5 | //
6 | // Consider what you can add to the Licensed trait.
7 | //
8 | // Execute `rustlings hint traits3` or use the `hint` watch subcommand for a
9 | // hint.
10 |
11 | // I AM NOT DONE
12 |
13 | pub trait Licensed {
14 | fn licensing_info(&self) -> String;
15 | }
16 |
17 | struct SomeSoftware {
18 | version_number: i32,
19 | }
20 |
21 | struct OtherSoftware {
22 | version_number: String,
23 | }
24 |
25 | impl Licensed for SomeSoftware {} // Don't edit this line
26 | impl Licensed for OtherSoftware {} // Don't edit this line
27 |
28 | #[cfg(test)]
29 | mod tests {
30 | use super::*;
31 |
32 | #[test]
33 | fn is_licensing_info_the_same() {
34 | let licensing_info = String::from("Some information");
35 | let some_software = SomeSoftware { version_number: 1 };
36 | let other_software = OtherSoftware {
37 | version_number: "v2.0.0".to_string(),
38 | };
39 | assert_eq!(some_software.licensing_info(), licensing_info);
40 | assert_eq!(other_software.licensing_info(), licensing_info);
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/exercises/traits/traits4.rs:
--------------------------------------------------------------------------------
1 | // traits4.rs
2 | //
3 | // Your task is to replace the '??' sections so the code compiles.
4 | //
5 | // Don't change any line other than the marked one.
6 | //
7 | // Execute `rustlings hint traits4` or use the `hint` watch subcommand for a
8 | // hint.
9 |
10 | // I AM NOT DONE
11 |
12 | pub trait Licensed {
13 | fn licensing_info(&self) -> String {
14 | "some information".to_string()
15 | }
16 | }
17 |
18 | struct SomeSoftware {}
19 |
20 | struct OtherSoftware {}
21 |
22 | impl Licensed for SomeSoftware {}
23 | impl Licensed for OtherSoftware {}
24 |
25 | // YOU MAY ONLY CHANGE THE NEXT LINE
26 | fn compare_license_types(software: ??, software_two: ??) -> bool {
27 | software.licensing_info() == software_two.licensing_info()
28 | }
29 |
30 | #[cfg(test)]
31 | mod tests {
32 | use super::*;
33 |
34 | #[test]
35 | fn compare_license_information() {
36 | let some_software = SomeSoftware {};
37 | let other_software = OtherSoftware {};
38 |
39 | assert!(compare_license_types(some_software, other_software));
40 | }
41 |
42 | #[test]
43 | fn compare_license_information_backwards() {
44 | let some_software = SomeSoftware {};
45 | let other_software = OtherSoftware {};
46 |
47 | assert!(compare_license_types(other_software, some_software));
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/exercises/vecs/README.md:
--------------------------------------------------------------------------------
1 | # Vectors
2 |
3 | Vectors are one of the most-used Rust data structures. In other programming
4 | languages, they'd simply be called Arrays, but since Rust operates on a
5 | bit of a lower level, an array in Rust is stored on the stack (meaning it
6 | can't grow or shrink, and the size needs to be known at compile time),
7 | and a Vector is stored in the heap (where these restrictions do not apply).
8 |
9 | Vectors are a bit of a later chapter in the book, but we think that they're
10 | useful enough to talk about them a bit earlier. We shall be talking about
11 | the other useful data structure, hash maps, later.
12 |
13 | ## Further information
14 |
15 | - [Storing Lists of Values with Vectors](https://doc.rust-lang.org/stable/book/ch08-01-vectors.html)
16 | - [`iter_mut`](https://doc.rust-lang.org/std/primitive.slice.html#method.iter_mut)
17 | - [`map`](https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.map)
18 |
--------------------------------------------------------------------------------
/exercises/vecs/vecs1.rs:
--------------------------------------------------------------------------------
1 | // vecs1.rs
2 | //
3 | // Your task is to create a `Vec` which holds the exact same elements as in the
4 | // array `a`.
5 | //
6 | // Make me compile and pass the test!
7 | //
8 | // Execute `rustlings hint vecs1` or use the `hint` watch subcommand for a hint.
9 |
10 | // I AM NOT DONE
11 |
12 | fn array_and_vec() -> ([i32; 4], Vec) {
13 | let a = [10, 20, 30, 40]; // a plain array
14 | let v = // TODO: declare your vector here with the macro for vectors
15 |
16 | (a, v)
17 | }
18 |
19 | #[cfg(test)]
20 | mod tests {
21 | use super::*;
22 |
23 | #[test]
24 | fn test_array_and_vec_similarity() {
25 | let (a, v) = array_and_vec();
26 | assert_eq!(a, v[..]);
27 | }
28 | }
--------------------------------------------------------------------------------
/exercises/vecs/vecs2.rs:
--------------------------------------------------------------------------------
1 | // vecs2.rs
2 | //
3 | // A Vec of even numbers is given. Your task is to complete the loop so that
4 | // each number in the Vec is multiplied by 2.
5 | //
6 | // Make me pass the test!
7 | //
8 | // Execute `rustlings hint vecs2` or use the `hint` watch subcommand for a hint.
9 |
10 | // I AM NOT DONE
11 |
12 | fn vec_loop(mut v: Vec) -> Vec {
13 | for element in v.iter_mut() {
14 | // TODO: Fill this up so that each element in the Vec `v` is
15 | // multiplied by 2.
16 | ???
17 | }
18 |
19 | // At this point, `v` should be equal to [4, 8, 12, 16, 20].
20 | v
21 | }
22 |
23 | fn vec_map(v: &Vec) -> Vec {
24 | v.iter().map(|element| {
25 | // TODO: Do the same thing as above - but instead of mutating the
26 | // Vec, you can just return the new number!
27 | ???
28 | }).collect()
29 | }
30 |
31 | #[cfg(test)]
32 | mod tests {
33 | use super::*;
34 |
35 | #[test]
36 | fn test_vec_loop() {
37 | let v: Vec = (1..).filter(|x| x % 2 == 0).take(5).collect();
38 | let ans = vec_loop(v.clone());
39 |
40 | assert_eq!(ans, v.iter().map(|x| x * 2).collect::>());
41 | }
42 |
43 | #[test]
44 | fn test_vec_map() {
45 | let v: Vec = (1..).filter(|x| x % 2 == 0).take(5).collect();
46 | let ans = vec_map(&v);
47 |
48 | assert_eq!(ans, v.iter().map(|x| x * 2).collect::>());
49 | }
50 | }
--------------------------------------------------------------------------------
/flake.lock:
--------------------------------------------------------------------------------
1 | {
2 | "nodes": {
3 | "flake-compat": {
4 | "flake": false,
5 | "locked": {
6 | "lastModified": 1650374568,
7 | "narHash": "sha256-Z+s0J8/r907g149rllvwhb4pKi8Wam5ij0st8PwAh+E=",
8 | "owner": "edolstra",
9 | "repo": "flake-compat",
10 | "rev": "b4a34015c698c7793d592d66adbab377907a2be8",
11 | "type": "github"
12 | },
13 | "original": {
14 | "owner": "edolstra",
15 | "repo": "flake-compat",
16 | "type": "github"
17 | }
18 | },
19 | "flake-utils": {
20 | "locked": {
21 | "lastModified": 1659877975,
22 | "narHash": "sha256-zllb8aq3YO3h8B/U0/J1WBgAL8EX5yWf5pMj3G0NAmc=",
23 | "owner": "numtide",
24 | "repo": "flake-utils",
25 | "rev": "c0e246b9b83f637f4681389ecabcb2681b4f3af0",
26 | "type": "github"
27 | },
28 | "original": {
29 | "owner": "numtide",
30 | "repo": "flake-utils",
31 | "type": "github"
32 | }
33 | },
34 | "nixpkgs": {
35 | "locked": {
36 | "lastModified": 1666629043,
37 | "narHash": "sha256-Yoq6Ut2F3Ol73yO9hG93x6ts5c4F5BhKTbcF3DtBEAw=",
38 | "owner": "nixos",
39 | "repo": "nixpkgs",
40 | "rev": "b39fd6e4edef83cb4a135ebef98751ce23becc33",
41 | "type": "github"
42 | },
43 | "original": {
44 | "owner": "nixos",
45 | "ref": "nixos-unstable",
46 | "repo": "nixpkgs",
47 | "type": "github"
48 | }
49 | },
50 | "root": {
51 | "inputs": {
52 | "flake-compat": "flake-compat",
53 | "flake-utils": "flake-utils",
54 | "nixpkgs": "nixpkgs"
55 | }
56 | }
57 | },
58 | "root": "root",
59 | "version": 7
60 | }
61 |
--------------------------------------------------------------------------------
/flake.nix:
--------------------------------------------------------------------------------
1 | {
2 | description = "Small exercises to get you used to reading and writing Rust code";
3 |
4 | inputs = {
5 | flake-compat = {
6 | url = "github:edolstra/flake-compat";
7 | flake = false;
8 | };
9 | flake-utils.url = "github:numtide/flake-utils";
10 | nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
11 | };
12 |
13 | outputs = { self, flake-utils, nixpkgs, ... }:
14 | flake-utils.lib.eachDefaultSystem (system:
15 | let
16 | pkgs = nixpkgs.legacyPackages.${system};
17 |
18 | cargoBuildInputs = with pkgs; lib.optionals stdenv.isDarwin [
19 | darwin.apple_sdk.frameworks.CoreServices
20 | ];
21 |
22 | rustlings =
23 | pkgs.rustPlatform.buildRustPackage {
24 | name = "rustlings";
25 | version = "5.5.1";
26 |
27 | buildInputs = cargoBuildInputs;
28 |
29 | src = with pkgs.lib; cleanSourceWith {
30 | src = self;
31 | # a function that returns a bool determining if the path should be included in the cleaned source
32 | filter = path: type:
33 | let
34 | # filename
35 | baseName = builtins.baseNameOf (toString path);
36 | # path from root directory
37 | path' = builtins.replaceStrings [ "${self}/" ] [ "" ] path;
38 | # checks if path is in the directory
39 | inDirectory = directory: hasPrefix directory path';
40 | in
41 | inDirectory "src" ||
42 | inDirectory "tests" ||
43 | hasPrefix "Cargo" baseName ||
44 | baseName == "info.toml";
45 | };
46 |
47 | cargoLock.lockFile = ./Cargo.lock;
48 | };
49 | in
50 | {
51 | devShell = pkgs.mkShell {
52 | RUST_SRC_PATH = "${pkgs.rust.packages.stable.rustPlatform.rustLibSrc}";
53 |
54 | buildInputs = with pkgs; [
55 | cargo
56 | rustc
57 | rust-analyzer
58 | rustlings
59 | rustfmt
60 | clippy
61 | ] ++ cargoBuildInputs;
62 | };
63 | });
64 | }
65 |
--------------------------------------------------------------------------------
/info.toml:
--------------------------------------------------------------------------------
1 | # INTRO
2 |
3 | # [[exercises]]
4 | # name = "intro1"
5 | # path = "exercises/intro/intro1.rs"
6 | # mode = "compile"
7 | # hint = """
8 | # Remove the I AM NOT DONE comment in the exercises/intro/intro1.rs file
9 | # to move on to the next exercise."""
10 |
11 |
12 | # VECS
13 |
14 | [[exercises]]
15 | name = "vecs1"
16 | path = "exercises/vecs/vecs1.rs"
17 | mode = "test"
18 | hint = """
19 | In Rust, there are two ways to define a Vector.
20 | 1. One way is to use the `Vec::new()` function to create a new vector
21 | and fill it with the `push()` method.
22 | 2. The second way, which is simpler is to use the `vec![]` macro and
23 | define your elements inside the square brackets.
24 | Check this chapter: https://doc.rust-lang.org/stable/book/ch08-01-vectors.html
25 | of the Rust book to learn more.
26 | """
27 |
28 | [[exercises]]
29 | name = "vecs2"
30 | path = "exercises/vecs/vecs2.rs"
31 | mode = "test"
32 | hint = """
33 | Hint 1: In the code, the variable `element` represents an item from the Vec as it is being iterated.
34 | Can you try multiplying this?
35 |
36 | Hint 2: For the first function, there's a way to directly access the numbers stored
37 | in the Vec, using the * dereference operator. You can both access and write to the
38 | number that way.
39 |
40 | After you've completed both functions, decide for yourself which approach you like
41 | better. What do you think is the more commonly used pattern under Rust developers?
42 | """
43 |
44 | # MOVE SEMANTICS
45 |
46 |
47 | [[exercises]]
48 | name = "move_semantics4"
49 | path = "exercises/move_semantics/move_semantics4.rs"
50 | mode = "compile"
51 | hint = """
52 | Stop reading whenever you feel like you have enough direction :) Or try
53 | doing one step and then fixing the compiler errors that result!
54 | So the end goal is to:
55 | - get rid of the first line in main that creates the new vector
56 | - so then `vec0` doesn't exist, so we can't pass it to `fill_vec`
57 | - `fill_vec` has had its signature changed, which our call should reflect
58 | - since we're not creating a new vec in `main` anymore, we need to create
59 | a new vec in `fill_vec`, similarly to the way we did in `main`"""
60 |
61 | [[exercises]]
62 | name = "move_semantics5"
63 | path = "exercises/move_semantics/move_semantics5.rs"
64 | mode = "compile"
65 | hint = """
66 | Carefully reason about the range in which each mutable reference is in
67 | scope. Does it help to update the value of referent (x) immediately after
68 | the mutable reference is taken? Read more about 'Mutable References'
69 | in the book's section References and Borrowing':
70 | https://doc.rust-lang.org/book/ch04-02-references-and-borrowing.html#mutable-references.
71 | """
72 |
73 | [[exercises]]
74 | name = "move_semantics6"
75 | path = "exercises/move_semantics/move_semantics6.rs"
76 | mode = "compile"
77 | hint = """
78 | To find the answer, you can consult the book section "References and Borrowing":
79 | https://doc.rust-lang.org/stable/book/ch04-02-references-and-borrowing.html
80 | The first problem is that `get_char` is taking ownership of the string.
81 | So `data` is moved and can't be used for `string_uppercase`
82 | `data` is moved to `get_char` first, meaning that `string_uppercase` cannot manipulate the data.
83 | Once you've fixed that, `string_uppercase`'s function signature will also need to be adjusted.
84 | Can you figure out how?
85 |
86 | Another hint: it has to do with the `&` character."""
87 |
88 | # STRUCTS
89 |
90 | [[exercises]]
91 | name = "structs1"
92 | path = "exercises/structs/structs1.rs"
93 | mode = "test"
94 | hint = """
95 | Rust has more than one type of struct. Three actually, all variants are used to package related data together.
96 | There are normal (or classic) structs. These are named collections of related data stored in fields.
97 | Tuple structs are basically just named tuples.
98 | Finally, Unit-like structs. These don't have any fields and are useful for generics.
99 |
100 | In this exercise you need to complete and implement one of each kind.
101 | Read more about structs in The Book: https://doc.rust-lang.org/book/ch05-01-defining-structs.html"""
102 |
103 |
104 | # STRINGS
105 |
106 |
107 | [[exercises]]
108 | name = "strings3"
109 | path = "exercises/strings/strings3.rs"
110 | mode = "test"
111 | hint = """
112 | There's tons of useful standard library functions for strings. Let's try and use some of
113 | them: !
114 |
115 | For the compose_me method: You can either use the `format!` macro, or convert the string
116 | slice into an owned string, which you can then freely extend."""
117 |
118 | [[exercises]]
119 | name = "strings4"
120 | path = "exercises/strings/strings4.rs"
121 | mode = "compile"
122 | hint = "No hints this time ;)"
123 |
124 | # MODULES
125 |
126 |
127 | [[exercises]]
128 | name = "modules2"
129 | path = "exercises/modules/modules2.rs"
130 | mode = "compile"
131 | hint = """
132 | The delicious_snacks module is trying to present an external interface that is
133 | different than its internal structure (the `fruits` and `veggies` modules and
134 | associated constants). Complete the `use` statements to fit the uses in main and
135 | find the one keyword missing for both constants.
136 | Learn more at https://doc.rust-lang.org/book/ch07-04-bringing-paths-into-scope-with-the-use-keyword.html#re-exporting-names-with-pub-use"""
137 |
138 | # HASHMAPS
139 |
140 | [[exercises]]
141 | name = "hashmaps1"
142 | path = "exercises/hashmaps/hashmaps1.rs"
143 | mode = "test"
144 | hint = """
145 | Hint 1: Take a look at the return type of the function to figure out
146 | the type for the `basket`.
147 | Hint 2: Number of fruits should be at least 5. And you have to put
148 | at least three different types of fruits.
149 | """
150 |
151 | [[exercises]]
152 | name = "hashmaps2"
153 | path = "exercises/hashmaps/hashmaps2.rs"
154 | mode = "test"
155 | hint = """
156 | Use the `entry()` and `or_insert()` methods of `HashMap` to achieve this.
157 | Learn more at https://doc.rust-lang.org/stable/book/ch08-03-hash-maps.html#only-inserting-a-value-if-the-key-has-no-value
158 | """
159 |
160 | [[exercises]]
161 | name = "hashmaps3"
162 | path = "exercises/hashmaps/hashmaps3.rs"
163 | mode = "test"
164 | hint = """
165 | Hint 1: Use the `entry()` and `or_insert()` methods of `HashMap` to insert entries corresponding to each team in the scores table.
166 | Learn more at https://doc.rust-lang.org/stable/book/ch08-03-hash-maps.html#only-inserting-a-value-if-the-key-has-no-value
167 | Hint 2: If there is already an entry for a given key, the value returned by `entry()` can be updated based on the existing value.
168 | Learn more at https://doc.rust-lang.org/book/ch08-03-hash-maps.html#updating-a-value-based-on-the-old-value
169 | """
170 |
171 | # QUIZ 2
172 |
173 | [[exercises]]
174 | name = "quiz2"
175 | path = "exercises/quiz2.rs"
176 | mode = "test"
177 | hint = "No hints this time ;)"
178 |
179 | # OPTIONS
180 |
181 | [[exercises]]
182 | name = "options1"
183 | path = "exercises/options/options1.rs"
184 | mode = "test"
185 | hint = """
186 | Options can have a Some value, with an inner value, or a None value, without an inner value.
187 | There's multiple ways to get at the inner value, you can use unwrap, or pattern match. Unwrapping
188 | is the easiest, but how do you do it safely so that it doesn't panic in your face later?"""
189 |
190 |
191 | # Generics
192 |
193 | [[exercises]]
194 | name = "generics1"
195 | path = "exercises/generics/generics1.rs"
196 | mode = "compile"
197 | hint = """
198 | Vectors in Rust make use of generics to create dynamically sized arrays of any type.
199 | You need to tell the compiler what type we are pushing onto this vector."""
200 |
201 | [[exercises]]
202 | name = "generics2"
203 | path = "exercises/generics/generics2.rs"
204 | mode = "test"
205 | hint = """
206 | Currently we are wrapping only values of type 'u32'.
207 | Maybe we could update the explicit references to this data type somehow?
208 |
209 | If you are still stuck https://doc.rust-lang.org/stable/book/ch10-01-syntax.html#in-method-definitions
210 | """
211 |
212 | # TRAITS
213 |
214 |
215 | [[exercises]]
216 | name = "traits3"
217 | path = "exercises/traits/traits3.rs"
218 | mode = "test"
219 | hint = """
220 | Traits can have a default implementation for functions. Structs that implement
221 | the trait can then use the default version of these functions if they choose not
222 | implement the function themselves.
223 |
224 | See the documentation at: https://doc.rust-lang.org/book/ch10-02-traits.html#default-implementations
225 | """
226 |
227 | [[exercises]]
228 | name = "traits4"
229 | path = "exercises/traits/traits4.rs"
230 | mode = "test"
231 | hint = """
232 | Instead of using concrete types as parameters you can use traits. Try replacing the
233 | '??' with 'impl '
234 |
235 | See the documentation at: https://doc.rust-lang.org/book/ch10-02-traits.html#traits-as-parameters
236 | """
237 |
238 |
239 | # QUIZ 3
240 |
241 | [[exercises]]
242 | name = "quiz3"
243 | path = "exercises/quiz3.rs"
244 | mode = "test"
245 | hint = """
246 | To find the best solution to this challenge you're going to need to think back to your
247 | knowledge of traits, specifically Trait Bound Syntax - you may also need this: `use std::fmt::Display;`."""
248 |
249 | # LIFETIMES
250 |
251 | [[exercises]]
252 | name = "lifetimes1"
253 | path = "exercises/lifetimes/lifetimes1.rs"
254 | mode = "compile"
255 | hint = """
256 | Let the compiler guide you. Also take a look at the book if you need help:
257 | https://doc.rust-lang.org/book/ch10-03-lifetime-syntax.html"""
258 |
259 | [[exercises]]
260 | name = "lifetimes2"
261 | path = "exercises/lifetimes/lifetimes2.rs"
262 | mode = "compile"
263 | hint = """
264 | Remember that the generic lifetime 'a will get the concrete lifetime that is equal to the smaller of the lifetimes of x and y.
265 | You can take at least two paths to achieve the desired result while keeping the inner block:
266 | 1. Move the string2 declaration to make it live as long as string1 (how is result declared?)
267 | 2. Move println! into the inner block"""
268 |
269 | [[exercises]]
270 | name = "lifetimes3"
271 | path = "exercises/lifetimes/lifetimes3.rs"
272 | mode = "compile"
273 | hint = """
274 | If you use a lifetime annotation in a struct's fields, where else does it need to be added?"""
275 |
276 | # STANDARD LIBRARY TYPES
277 |
278 | [[exercises]]
279 | name = "iterators1"
280 | path = "exercises/iterators/iterators1.rs"
281 | mode = "compile"
282 | hint = """
283 | Step 1:
284 | We need to apply something to the collection `my_fav_fruits` before we start to go through
285 | it. What could that be? Take a look at the struct definition for a vector for inspiration:
286 | https://doc.rust-lang.org/std/vec/struct.Vec.html
287 | Step 2 & step 3:
288 | Very similar to the lines above and below. You've got this!
289 | Step 4:
290 | An iterator goes through all elements in a collection, but what if we've run out of
291 | elements? What should we expect here? If you're stuck, take a look at
292 | https://doc.rust-lang.org/std/iter/trait.Iterator.html for some ideas.
293 | """
294 |
295 | [[exercises]]
296 | name = "iterators2"
297 | path = "exercises/iterators/iterators2.rs"
298 | mode = "test"
299 | hint = """
300 | Step 1
301 | The variable `first` is a `char`. It needs to be capitalized and added to the
302 | remaining characters in `c` in order to return the correct `String`.
303 | The remaining characters in `c` can be viewed as a string slice using the
304 | `as_str` method.
305 | The documentation for `char` contains many useful methods.
306 | https://doc.rust-lang.org/std/primitive.char.html
307 |
308 | Step 2
309 | Create an iterator from the slice. Transform the iterated values by applying
310 | the `capitalize_first` function. Remember to collect the iterator.
311 |
312 | Step 3.
313 | This is surprisingly similar to the previous solution. Collect is very powerful
314 | and very general. Rust just needs to know the desired type."""
315 |
316 | # SMART POINTERS
317 |
318 | [[exercises]]
319 | name = "box1"
320 | path = "exercises/smart_pointers/box1.rs"
321 | mode = "test"
322 | hint = """
323 | Step 1
324 | The compiler's message should help: since we cannot store the value of the actual type
325 | when working with recursive types, we need to store a reference (pointer) to its value.
326 | We should, therefore, place our `List` inside a `Box`. More details in the book here:
327 | https://doc.rust-lang.org/book/ch15-01-box.html#enabling-recursive-types-with-boxes
328 |
329 | Step 2
330 | Creating an empty list should be fairly straightforward (hint: peek at the assertions).
331 | For a non-empty list keep in mind that we want to use our Cons "list builder".
332 | Although the current list is one of integers (i32), feel free to change the definition
333 | and try other types!
334 | """
335 |
336 | [[exercises]]
337 | name = "rc1"
338 | path = "exercises/smart_pointers/rc1.rs"
339 | mode = "compile"
340 | hint = """
341 | This is a straightforward exercise to use the Rc type. Each Planet has
342 | ownership of the Sun, and uses Rc::clone() to increment the reference count of the Sun.
343 | After using drop() to move the Planets out of scope individually, the reference count goes down.
344 | In the end the sun only has one reference again, to itself. See more at:
345 | https://doc.rust-lang.org/book/ch15-04-rc.html
346 |
347 | * Unfortunately Pluto is no longer considered a planet :(
348 | """
349 |
350 | [[exercises]]
351 | name = "arc1"
352 | path = "exercises/smart_pointers/arc1.rs"
353 | mode = "compile"
354 | hint = """
355 | Make `shared_numbers` be an `Arc` from the numbers vector. Then, in order
356 | to avoid creating a copy of `numbers`, you'll need to create `child_numbers`
357 | inside the loop but still in the main thread.
358 |
359 | `child_numbers` should be a clone of the Arc of the numbers instead of a
360 | thread-local copy of the numbers.
361 |
362 | This is a simple exercise if you understand the underlying concepts, but if this
363 | is too much of a struggle, consider reading through all of Chapter 16 in the book:
364 | https://doc.rust-lang.org/stable/book/ch16-00-concurrency.html
365 | """
366 |
367 | [[exercises]]
368 | name = "cow1"
369 | path = "exercises/smart_pointers/cow1.rs"
370 | mode = "test"
371 | hint = """
372 | If Cow already owns the data it doesn't need to clone it when to_mut() is called.
373 |
374 | Check out https://doc.rust-lang.org/std/borrow/enum.Cow.html for documentation
375 | on the `Cow` type.
376 | """
377 |
378 | # MACROS
379 |
380 | [[exercises]]
381 | name = "macros1"
382 | path = "exercises/macros/macros1.rs"
383 | mode = "compile"
384 | hint = """
385 | When you call a macro, you need to add something special compared to a
386 | regular function call. If you're stuck, take a look at what's inside
387 | `my_macro`."""
388 |
389 | [[exercises]]
390 | name = "macros2"
391 | path = "exercises/macros/macros2.rs"
392 | mode = "compile"
393 | hint = """
394 | Macros don't quite play by the same rules as the rest of Rust, in terms of
395 | what's available where.
396 |
397 | Unlike other things in Rust, the order of "where you define a macro" versus
398 | "where you use it" actually matters."""
399 |
400 |
401 |
402 |
403 |
404 |
--------------------------------------------------------------------------------
/install.ps1:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env pwsh
2 |
3 | #Requires -Version 5
4 | param($path = "$home/rustlings")
5 |
6 | Write-Host "Let's get you set up with Rustlings!"
7 |
8 | Write-Host "Checking requirements..."
9 | if (Get-Command git -ErrorAction SilentlyContinue) {
10 | Write-Host "SUCCESS: Git is installed"
11 | } else {
12 | Write-Host "WARNING: Git does not seem to be installed."
13 | Write-Host "Please download Git using your package manager or over https://git-scm.com/!"
14 | exit 1
15 | }
16 |
17 | if (Get-Command rustc -ErrorAction SilentlyContinue) {
18 | Write-Host "SUCCESS: Rust is installed"
19 | } else {
20 | Write-Host "WARNING: Rust does not seem to be installed."
21 | Write-Host "Please download Rust using https://rustup.rs!"
22 | exit 1
23 | }
24 |
25 | if (Get-Command cargo -ErrorAction SilentlyContinue) {
26 | Write-Host "SUCCESS: Cargo is installed"
27 | } else {
28 | Write-Host "WARNING: Cargo does not seem to be installed."
29 | Write-Host "Please download Rust and Cargo using https://rustup.rs!"
30 | exit 1
31 | }
32 |
33 | # Function that compares two versions strings v1 and v2 given in arguments (e.g 1.31 and 1.33.0).
34 | # Returns 1 if v1 > v2, 0 if v1 == v2, 2 if v1 < v2.
35 | function vercomp($v1, $v2) {
36 | if ($v1 -eq $v2) {
37 | return 0
38 | }
39 |
40 | $v1 = $v1.Replace(".", "0")
41 | $v2 = $v2.Replace(".", "0")
42 | if ($v1.Length -gt $v2.Length) {
43 | $v2 = $v2.PadRight($v1.Length, "0")
44 | } else {
45 | $v1 = $v1.PadRight($v2.Length, "0")
46 | }
47 |
48 | if ($v1 -gt $v2) {
49 | return 1
50 | } else {
51 | return 2
52 | }
53 | }
54 |
55 | $rustVersion = $(rustc --version).Split(" ")[1]
56 | $minRustVersion = "1.56"
57 | if ((vercomp $rustVersion $minRustVersion) -eq 2) {
58 | Write-Host "WARNING: Rust version is too old: $rustVersion - needs at least $minRustVersion"
59 | Write-Host "Please update Rust with 'rustup update'"
60 | exit 1
61 | } else {
62 | Write-Host "SUCCESS: Rust is up to date"
63 | }
64 |
65 | Write-Host "Cloning Rustlings at $path"
66 | git clone -q https://github.com/rust-lang/rustlings $path
67 | if (!($LASTEXITCODE -eq 0)) {
68 | exit 1
69 | }
70 |
71 | # UseBasicParsing is deprecated, pwsh 6 or above will automatically use it,
72 | # but anyone running pwsh 5 will have to pass the argument.
73 | $version = Invoke-WebRequest -UseBasicParsing https://api.github.com/repos/rust-lang/rustlings/releases/latest `
74 | | ConvertFrom-Json | Select-Object -ExpandProperty tag_name
75 |
76 | Write-Host "Checking out version $version..."
77 | Set-Location $path
78 | git checkout -q tags/$version
79 |
80 | Write-Host "Installing the 'rustlings' executable..."
81 | cargo install --force --path .
82 | if (!(Get-Command rustlings -ErrorAction SilentlyContinue)) {
83 | Write-Host "WARNING: Please check that you have '~/.cargo/bin' in your PATH environment variable!"
84 | }
85 |
86 | # Checking whether Clippy is installed.
87 | # Due to a bug in Cargo, this must be done with Rustup: https://github.com/rust-lang/rustup/issues/1514
88 | $clippy = (rustup component list | Select-String "clippy" | Select-String "installed") | Out-String
89 | if (!$clippy) {
90 | Write-Host "Installing the 'cargo-clippy' executable..."
91 | rustup component add clippy
92 | }
93 |
94 | Write-Host "All done! Navigate to $path and run 'rustlings' to get started!"
95 |
--------------------------------------------------------------------------------
/install.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | set -euo pipefail
3 |
4 | echo "Let's get you set up with Rustlings!"
5 |
6 | echo "Checking requirements..."
7 | if [ -x "$(command -v git)" ]
8 | then
9 | echo "SUCCESS: Git is installed"
10 | else
11 | echo "ERROR: Git does not seem to be installed."
12 | echo "Please download Git using your package manager or over https://git-scm.com/!"
13 | exit 1
14 | fi
15 |
16 | if [ -x "$(command -v cc)" ]
17 | then
18 | echo "SUCCESS: cc is installed"
19 | else
20 | echo "ERROR: cc does not seem to be installed."
21 | echo "Please download (g)cc using your package manager."
22 | echo "OSX: xcode-select --install"
23 | echo "Deb: sudo apt install gcc"
24 | echo "Yum: sudo yum -y install gcc"
25 | exit 1
26 | fi
27 |
28 | if [ -x "$(command -v rustup)" ]
29 | then
30 | echo "SUCCESS: rustup is installed"
31 | else
32 | echo "ERROR: rustup does not seem to be installed."
33 | echo "Please download rustup using https://rustup.rs!"
34 | exit 1
35 | fi
36 |
37 | if [ -x "$(command -v rustc)" ]
38 | then
39 | echo "SUCCESS: Rust is installed"
40 | else
41 | echo "ERROR: Rust does not seem to be installed."
42 | echo "Please download Rust using rustup!"
43 | exit 1
44 | fi
45 |
46 | if [ -x "$(command -v cargo)" ]
47 | then
48 | echo "SUCCESS: Cargo is installed"
49 | else
50 | echo "ERROR: Cargo does not seem to be installed."
51 | echo "Please download Rust and Cargo using rustup!"
52 | exit 1
53 | fi
54 |
55 | # Look up python installations, starting with 3 with a fallback of 2
56 | if [ -x "$(command -v python3)" ]
57 | then
58 | PY="$(command -v python3)"
59 | elif [ -x "$(command -v python)" ]
60 | then
61 | PY="$(command -v python)"
62 | elif [ -x "$(command -v python2)" ]
63 | then
64 | PY="$(command -v python2)"
65 | else
66 | echo "ERROR: No working python installation was found"
67 | echo "Please install python and add it to the PATH variable"
68 | exit 1
69 | fi
70 |
71 | # Function that compares two versions strings v1 and v2 given in arguments (e.g 1.31 and 1.33.0).
72 | # Returns 1 if v1 > v2, 0 if v1 == v2, 2 if v1 < v2.
73 | function vercomp() {
74 | if [[ $1 == $2 ]]
75 | then
76 | return 0
77 | fi
78 | v1=( ${1//./ } )
79 | v2=( ${2//./ } )
80 | len1=${#v1[@]}
81 | len2=${#v2[@]}
82 | max_len=$len1
83 | if [[ $max_len -lt $len2 ]]
84 | then
85 | max_len=$len2
86 | fi
87 |
88 | #pad right in short arr
89 | if [[ len1 -gt len2 ]];
90 | then
91 | for ((i = len2; i < len1; i++));
92 | do
93 | v2[$i]=0
94 | done
95 | else
96 | for ((i = len1; i < len2; i++));
97 | do
98 | v1[$i]=0
99 | done
100 | fi
101 |
102 | for i in `seq 0 $((max_len-1))`
103 | do
104 | # Fill empty fields with zeros in v1
105 | if [ -z "${v1[$i]}" ]
106 | then
107 | v1[$i]=0
108 | fi
109 | # And in v2
110 | if [ -z "${v2[$i]}" ]
111 | then
112 | v2[$i]=0
113 | fi
114 | if [ ${v1[$i]} -gt ${v2[$i]} ]
115 | then
116 | return 1
117 | fi
118 | if [ ${v1[$i]} -lt ${v2[$i]} ]
119 | then
120 | return 2
121 | fi
122 | done
123 | return 0
124 | }
125 |
126 | RustVersion=$(rustc --version | cut -d " " -f 2)
127 | MinRustVersion=1.58
128 | vercomp "$RustVersion" $MinRustVersion || ec=$?
129 | if [ ${ec:-0} -eq 2 ]
130 | then
131 | echo "ERROR: Rust version is too old: $RustVersion - needs at least $MinRustVersion"
132 | echo "Please update Rust with 'rustup update'"
133 | exit 1
134 | else
135 | echo "SUCCESS: Rust is up to date"
136 | fi
137 |
138 | Path=${1:-rustlings/}
139 | echo "Cloning Rustlings at $Path..."
140 | git clone -q https://github.com/rust-lang/rustlings "$Path"
141 |
142 | cd "$Path"
143 |
144 | Version=$(curl -s https://api.github.com/repos/rust-lang/rustlings/releases/latest | ${PY} -c "import json,sys;obj=json.load(sys.stdin);print(obj['tag_name']) if 'tag_name' in obj else sys.exit(f\"Error: {obj['message']}\");")
145 | CargoBin="${CARGO_HOME:-$HOME/.cargo}/bin"
146 |
147 | if [[ -z ${Version} ]]
148 | then
149 | echo "The latest tag version could not be fetched remotely."
150 | echo "Using the local git repository..."
151 | Version=$(ls -tr .git/refs/tags/ | tail -1)
152 | if [[ -z ${Version} ]]
153 | then
154 | echo "No valid tag version found"
155 | echo "Rustlings will be installed using the main branch"
156 | Version="main"
157 | else
158 | Version="tags/${Version}"
159 | fi
160 | else
161 | Version="tags/${Version}"
162 | fi
163 |
164 | echo "Checking out version $Version..."
165 | git checkout -q ${Version}
166 |
167 | echo "Installing the 'rustlings' executable..."
168 | cargo install --force --path .
169 |
170 | if ! [ -x "$(command -v rustlings)" ]
171 | then
172 | echo "WARNING: Please check that you have '$CargoBin' in your PATH environment variable!"
173 | fi
174 |
175 | # Checking whether Clippy is installed.
176 | # Due to a bug in Cargo, this must be done with Rustup: https://github.com/rust-lang/rustup/issues/1514
177 | Clippy=$(rustup component list | grep "clippy" | grep "installed")
178 | if [ -z "$Clippy" ]
179 | then
180 | echo "Installing the 'cargo-clippy' executable..."
181 | rustup component add clippy
182 | fi
183 |
184 | echo "All done! Run 'rustlings' to get started."
185 |
--------------------------------------------------------------------------------
/oranda.json:
--------------------------------------------------------------------------------
1 | {
2 | "project": {
3 | "homepage": "https://rustlings.cool",
4 | "repository": "https://github.com/rust-lang/rustlings"
5 | },
6 | "marketing": {
7 | "analytics": {
8 | "plausible": {
9 | "domain": "rustlings.cool"
10 | }
11 | }
12 | },
13 | "components": {
14 | "artifacts": {
15 | "cargo_dist": false,
16 | "package_managers": {
17 | "preferred": {
18 | "macos/linux/unix": "curl -L https://raw.githubusercontent.com/rust-lang/rustlings/main/install.sh | bash",
19 | "windows": "Start-BitsTransfer -Source https://raw.githubusercontent.com/rust-lang/rustlings/main/install.ps1 -Destination $env:TMP/install_rustlings.ps1; Unblock-File $env:TMP/install_rustlings.ps1; Invoke-Expression $env:TMP/install_rustlings.ps1"
20 | }
21 | }
22 | },
23 | "changelog": true
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/shell.nix:
--------------------------------------------------------------------------------
1 | (import (let lock = builtins.fromJSON (builtins.readFile ./flake.lock);
2 | in fetchTarball {
3 | url =
4 | "https://github.com/edolstra/flake-compat/archive/${lock.nodes.flake-compat.locked.rev}.tar.gz";
5 | sha256 = lock.nodes.flake-compat.locked.narHash;
6 | }) { src = ./.; }).shellNix
7 |
--------------------------------------------------------------------------------
/src/exercise.rs:
--------------------------------------------------------------------------------
1 | use regex::Regex;
2 | use serde::Deserialize;
3 | use std::env;
4 | use std::fmt::{self, Display, Formatter};
5 | use std::fs::{self, remove_file, File};
6 | use std::io::Read;
7 | use std::path::PathBuf;
8 | use std::process::{self, Command};
9 |
10 | const RUSTC_COLOR_ARGS: &[&str] = &["--color", "always"];
11 | const RUSTC_EDITION_ARGS: &[&str] = &["--edition", "2021"];
12 | const I_AM_DONE_REGEX: &str = r"(?m)^\s*///?\s*I\s+AM\s+NOT\s+DONE";
13 | const CONTEXT: usize = 2;
14 | const CLIPPY_CARGO_TOML_PATH: &str = "./exercises/clippy/Cargo.toml";
15 | const BUILD_SCRIPT_CARGO_TOML_PATH: &str = "./exercises/tests/Cargo.toml";
16 |
17 | // Get a temporary file name that is hopefully unique
18 | #[inline]
19 | fn temp_file() -> String {
20 | let thread_id: String = format!("{:?}", std::thread::current().id())
21 | .chars()
22 | .filter(|c| c.is_alphanumeric())
23 | .collect();
24 |
25 | format!("./temp_{}_{thread_id}", process::id())
26 | }
27 |
28 | // The mode of the exercise.
29 | #[derive(Deserialize, Copy, Clone, Debug)]
30 | #[serde(rename_all = "lowercase")]
31 | pub enum Mode {
32 | // Indicates that the exercise should be compiled as a binary
33 | Compile,
34 | // Indicates that the exercise should be compiled as a test harness
35 | Test,
36 | // Indicates that the exercise should be linted with clippy
37 | Clippy,
38 | // Indicates that the exercise should be run using cargo with build script
39 | BuildScript,
40 | }
41 |
42 | #[derive(Deserialize)]
43 | pub struct ExerciseList {
44 | pub exercises: Vec,
45 | }
46 |
47 | // A representation of a rustlings exercise.
48 | // This is deserialized from the accompanying info.toml file
49 | #[derive(Deserialize, Debug)]
50 | pub struct Exercise {
51 | // Name of the exercise
52 | pub name: String,
53 | // The path to the file containing the exercise's source code
54 | pub path: PathBuf,
55 | // The mode of the exercise (Test, Compile, or Clippy)
56 | pub mode: Mode,
57 | // The hint text associated with the exercise
58 | pub hint: String,
59 | }
60 |
61 | // An enum to track of the state of an Exercise.
62 | // An Exercise can be either Done or Pending
63 | #[derive(PartialEq, Debug)]
64 | pub enum State {
65 | // The state of the exercise once it's been completed
66 | Done,
67 | // The state of the exercise while it's not completed yet
68 | Pending(Vec),
69 | }
70 |
71 | // The context information of a pending exercise
72 | #[derive(PartialEq, Debug)]
73 | pub struct ContextLine {
74 | // The source code that is still pending completion
75 | pub line: String,
76 | // The line number of the source code still pending completion
77 | pub number: usize,
78 | // Whether or not this is important
79 | pub important: bool,
80 | }
81 |
82 | // The result of compiling an exercise
83 | pub struct CompiledExercise<'a> {
84 | exercise: &'a Exercise,
85 | _handle: FileHandle,
86 | }
87 |
88 | impl<'a> CompiledExercise<'a> {
89 | // Run the compiled exercise
90 | pub fn run(&self) -> Result {
91 | self.exercise.run()
92 | }
93 | }
94 |
95 | // A representation of an already executed binary
96 | #[derive(Debug)]
97 | pub struct ExerciseOutput {
98 | // The textual contents of the standard output of the binary
99 | pub stdout: String,
100 | // The textual contents of the standard error of the binary
101 | pub stderr: String,
102 | }
103 |
104 | struct FileHandle;
105 |
106 | impl Drop for FileHandle {
107 | fn drop(&mut self) {
108 | clean();
109 | }
110 | }
111 |
112 | impl Exercise {
113 | pub fn compile(&self) -> Result {
114 | let cmd = match self.mode {
115 | Mode::Compile => Command::new("rustc")
116 | .args(&[self.path.to_str().unwrap(), "-o", &temp_file()])
117 | .args(RUSTC_COLOR_ARGS)
118 | .args(RUSTC_EDITION_ARGS)
119 | .output(),
120 | Mode::Test => Command::new("rustc")
121 | .args(&["--test", self.path.to_str().unwrap(), "-o", &temp_file()])
122 | .args(RUSTC_COLOR_ARGS)
123 | .args(RUSTC_EDITION_ARGS)
124 | .output(),
125 | Mode::Clippy => {
126 | let cargo_toml = format!(
127 | r#"[package]
128 | name = "{}"
129 | version = "0.0.1"
130 | edition = "2021"
131 | [[bin]]
132 | name = "{}"
133 | path = "{}.rs""#,
134 | self.name, self.name, self.name
135 | );
136 | let cargo_toml_error_msg = if env::var("NO_EMOJI").is_ok() {
137 | "Failed to write Clippy Cargo.toml file."
138 | } else {
139 | "Failed to write 📎 Clippy 📎 Cargo.toml file."
140 | };
141 | fs::write(CLIPPY_CARGO_TOML_PATH, cargo_toml).expect(cargo_toml_error_msg);
142 | // To support the ability to run the clippy exercises, build
143 | // an executable, in addition to running clippy. With a
144 | // compilation failure, this would silently fail. But we expect
145 | // clippy to reflect the same failure while compiling later.
146 | Command::new("rustc")
147 | .args(&[self.path.to_str().unwrap(), "-o", &temp_file()])
148 | .args(RUSTC_COLOR_ARGS)
149 | .args(RUSTC_EDITION_ARGS)
150 | .output()
151 | .expect("Failed to compile!");
152 | // Due to an issue with Clippy, a cargo clean is required to catch all lints.
153 | // See https://github.com/rust-lang/rust-clippy/issues/2604
154 | // This is already fixed on Clippy's master branch. See this issue to track merging into Cargo:
155 | // https://github.com/rust-lang/rust-clippy/issues/3837
156 | Command::new("cargo")
157 | .args(&["clean", "--manifest-path", CLIPPY_CARGO_TOML_PATH])
158 | .args(RUSTC_COLOR_ARGS)
159 | .output()
160 | .expect("Failed to run 'cargo clean'");
161 | Command::new("cargo")
162 | .args(&["clippy", "--manifest-path", CLIPPY_CARGO_TOML_PATH])
163 | .args(RUSTC_COLOR_ARGS)
164 | .args(&["--", "-D", "warnings", "-D", "clippy::float_cmp"])
165 | .output()
166 | },
167 | Mode::BuildScript => {
168 | let cargo_toml = format!(
169 | r#"[package]
170 | name = "{}"
171 | version = "0.0.1"
172 | edition = "2021"
173 | [[bin]]
174 | name = "{}"
175 | path = "{}.rs""#,
176 | self.name, self.name, self.name
177 | );
178 | let cargo_toml_error_msg = if env::var("NO_EMOJI").is_ok() {
179 | "Failed to write Clippy Cargo.toml file."
180 | } else {
181 | "Failed to write 📎 Clippy 📎 Cargo.toml file."
182 | };
183 | fs::write(BUILD_SCRIPT_CARGO_TOML_PATH, cargo_toml).expect(cargo_toml_error_msg);
184 |
185 | Command::new("cargo")
186 | .args(&["test", "--manifest-path", BUILD_SCRIPT_CARGO_TOML_PATH])
187 | .output()
188 | }
189 | }
190 | .expect("Failed to run 'compile' command.");
191 |
192 | if cmd.status.success() {
193 | Ok(CompiledExercise {
194 | exercise: self,
195 | _handle: FileHandle,
196 | })
197 | } else {
198 | clean();
199 | Err(ExerciseOutput {
200 | stdout: String::from_utf8_lossy(&cmd.stdout).to_string(),
201 | stderr: String::from_utf8_lossy(&cmd.stderr).to_string(),
202 | })
203 | }
204 | }
205 |
206 | fn run(&self) -> Result {
207 | let arg = match self.mode {
208 | Mode::Test => "--show-output",
209 | Mode::BuildScript => return Ok(ExerciseOutput {
210 | stdout: "".to_string(),
211 | stderr: "".to_string(),
212 | }),
213 | _ => "",
214 | };
215 | println!("pa={}", temp_file());
216 | let cmd = Command::new(&temp_file())
217 | .arg(arg)
218 | .output()
219 | .expect("Failed to run 'run' command");
220 |
221 | let output = ExerciseOutput {
222 | stdout: String::from_utf8_lossy(&cmd.stdout).to_string(),
223 | stderr: String::from_utf8_lossy(&cmd.stderr).to_string(),
224 | };
225 |
226 | if cmd.status.success() {
227 | Ok(output)
228 | } else {
229 | Err(output)
230 | }
231 | }
232 |
233 | pub fn state(&self) -> State {
234 | let mut source_file =
235 | File::open(&self.path).expect("We were unable to open the exercise file!");
236 |
237 | let source = {
238 | let mut s = String::new();
239 | source_file
240 | .read_to_string(&mut s)
241 | .expect("We were unable to read the exercise file!");
242 | s
243 | };
244 |
245 | let re = Regex::new(I_AM_DONE_REGEX).unwrap();
246 |
247 | if !re.is_match(&source) {
248 | return State::Done;
249 | }
250 |
251 | let matched_line_index = source
252 | .lines()
253 | .enumerate()
254 | .find_map(|(i, line)| if re.is_match(line) { Some(i) } else { None })
255 | .expect("This should not happen at all");
256 |
257 | let min_line = ((matched_line_index as i32) - (CONTEXT as i32)).max(0) as usize;
258 | let max_line = matched_line_index + CONTEXT;
259 |
260 | let context = source
261 | .lines()
262 | .enumerate()
263 | .filter(|&(i, _)| i >= min_line && i <= max_line)
264 | .map(|(i, line)| ContextLine {
265 | line: line.to_string(),
266 | number: i + 1,
267 | important: i == matched_line_index,
268 | })
269 | .collect();
270 |
271 | State::Pending(context)
272 | }
273 |
274 | // Check that the exercise looks to be solved using self.state()
275 | // This is not the best way to check since
276 | // the user can just remove the "I AM NOT DONE" string from the file
277 | // without actually having solved anything.
278 | // The only other way to truly check this would to compile and run
279 | // the exercise; which would be both costly and counterintuitive
280 | pub fn looks_done(&self) -> bool {
281 | self.state() == State::Done
282 | }
283 | }
284 |
285 | impl Display for Exercise {
286 | fn fmt(&self, f: &mut Formatter) -> fmt::Result {
287 | write!(f, "{}", self.path.to_str().unwrap())
288 | }
289 | }
290 |
291 | #[inline]
292 | fn clean() {
293 | let _ignored = remove_file(&temp_file());
294 | }
295 |
296 | #[cfg(test)]
297 | mod test {
298 | use super::*;
299 | use std::path::Path;
300 |
301 | #[test]
302 | fn test_clean() {
303 | File::create(&temp_file()).unwrap();
304 | let exercise = Exercise {
305 | name: String::from("example"),
306 | path: PathBuf::from("tests/fixture/state/pending_exercise.rs"),
307 | mode: Mode::Compile,
308 | hint: String::from(""),
309 | };
310 | let compiled = exercise.compile().unwrap();
311 | drop(compiled);
312 | assert!(!Path::new(&temp_file()).exists());
313 | }
314 |
315 | #[test]
316 | fn test_pending_state() {
317 | let exercise = Exercise {
318 | name: "pending_exercise".into(),
319 | path: PathBuf::from("tests/fixture/state/pending_exercise.rs"),
320 | mode: Mode::Compile,
321 | hint: String::new(),
322 | };
323 |
324 | let state = exercise.state();
325 | let expected = vec![
326 | ContextLine {
327 | line: "// fake_exercise".to_string(),
328 | number: 1,
329 | important: false,
330 | },
331 | ContextLine {
332 | line: "".to_string(),
333 | number: 2,
334 | important: false,
335 | },
336 | ContextLine {
337 | line: "// I AM NOT DONE".to_string(),
338 | number: 3,
339 | important: true,
340 | },
341 | ContextLine {
342 | line: "".to_string(),
343 | number: 4,
344 | important: false,
345 | },
346 | ContextLine {
347 | line: "fn main() {".to_string(),
348 | number: 5,
349 | important: false,
350 | },
351 | ];
352 |
353 | assert_eq!(state, State::Pending(expected));
354 | }
355 |
356 | #[test]
357 | fn test_finished_exercise() {
358 | let exercise = Exercise {
359 | name: "finished_exercise".into(),
360 | path: PathBuf::from("tests/fixture/state/finished_exercise.rs"),
361 | mode: Mode::Compile,
362 | hint: String::new(),
363 | };
364 |
365 | assert_eq!(exercise.state(), State::Done);
366 | }
367 |
368 | #[test]
369 | fn test_exercise_with_output() {
370 | let exercise = Exercise {
371 | name: "exercise_with_output".into(),
372 | path: PathBuf::from("tests/fixture/success/testSuccess.rs"),
373 | mode: Mode::Test,
374 | hint: String::new(),
375 | };
376 | let out = exercise.compile().unwrap().run().unwrap();
377 | assert!(out.stdout.contains("THIS TEST TOO SHALL PASS"));
378 | }
379 | }
380 |
--------------------------------------------------------------------------------
/src/main.rs:
--------------------------------------------------------------------------------
1 | use crate::exercise::{Exercise, ExerciseList};
2 | use crate::project::RustAnalyzerProject;
3 | use crate::run::{reset, run};
4 | use crate::verify::verify;
5 | use argh::FromArgs;
6 | use console::Emoji;
7 | use notify::DebouncedEvent;
8 | use notify::{RecommendedWatcher, RecursiveMode, Watcher};
9 | use serde::{Deserialize, Serialize};
10 | use std::ffi::OsStr;
11 | use std::fs;
12 | use std::io::{self, prelude::*};
13 | use std::path::Path;
14 | use std::process::{Command, Stdio};
15 | use std::sync::atomic::{AtomicBool, Ordering};
16 | use std::sync::mpsc::{channel, RecvTimeoutError};
17 | use std::sync::{Arc, Mutex};
18 | use std::thread;
19 | use std::time::Duration;
20 | use std::time::{UNIX_EPOCH, SystemTime};
21 |
22 | #[macro_use]
23 | mod ui;
24 |
25 | mod exercise;
26 | mod project;
27 | mod run;
28 | mod verify;
29 |
30 | // In sync with crate version
31 | const VERSION: &str = "5.5.1";
32 |
33 | #[derive(FromArgs, PartialEq, Debug)]
34 | /// Rustlings is a collection of small exercises to get you used to writing and reading Rust code
35 | struct Args {
36 | /// show outputs from the test exercises
37 | #[argh(switch)]
38 | nocapture: bool,
39 | /// show the executable version
40 | #[argh(switch, short = 'v')]
41 | version: bool,
42 | #[argh(subcommand)]
43 | nested: Option,
44 | }
45 |
46 | #[derive(FromArgs, PartialEq, Debug)]
47 | #[argh(subcommand)]
48 | enum Subcommands {
49 | Verify(VerifyArgs),
50 | Watch(WatchArgs),
51 | Run(RunArgs),
52 | Reset(ResetArgs),
53 | Hint(HintArgs),
54 | List(ListArgs),
55 | Lsp(LspArgs),
56 | CicvVerify(CicvVerifyArgs)
57 | }
58 |
59 | #[derive(FromArgs, PartialEq, Debug)]
60 | #[argh(subcommand, name = "cicvverify", description = "cicvverify")]
61 | struct CicvVerifyArgs {}
62 |
63 | #[derive(FromArgs, PartialEq, Debug)]
64 | #[argh(subcommand, name = "verify")]
65 | /// Verifies all exercises according to the recommended order
66 | struct VerifyArgs {}
67 |
68 | #[derive(FromArgs, PartialEq, Debug)]
69 | #[argh(subcommand, name = "watch")]
70 | /// Reruns `verify` when files were edited
71 | struct WatchArgs {
72 | /// show hints on success
73 | #[argh(switch)]
74 | success_hints: bool,
75 | }
76 |
77 | #[derive(FromArgs, PartialEq, Debug)]
78 | #[argh(subcommand, name = "run")]
79 | /// Runs/Tests a single exercise
80 | struct RunArgs {
81 | #[argh(positional)]
82 | /// the name of the exercise
83 | name: String,
84 | }
85 |
86 | #[derive(FromArgs, PartialEq, Debug)]
87 | #[argh(subcommand, name = "reset")]
88 | /// Resets a single exercise using "git stash -- "
89 | struct ResetArgs {
90 | #[argh(positional)]
91 | /// the name of the exercise
92 | name: String,
93 | }
94 |
95 | #[derive(FromArgs, PartialEq, Debug)]
96 | #[argh(subcommand, name = "hint")]
97 | /// Returns a hint for the given exercise
98 | struct HintArgs {
99 | #[argh(positional)]
100 | /// the name of the exercise
101 | name: String,
102 | }
103 |
104 | #[derive(FromArgs, PartialEq, Debug)]
105 | #[argh(subcommand, name = "lsp")]
106 | /// Enable rust-analyzer for exercises
107 | struct LspArgs {}
108 |
109 | #[derive(FromArgs, PartialEq, Debug)]
110 | #[argh(subcommand, name = "list")]
111 | /// Lists the exercises available in Rustlings
112 | struct ListArgs {
113 | #[argh(switch, short = 'p')]
114 | /// show only the paths of the exercises
115 | paths: bool,
116 | #[argh(switch, short = 'n')]
117 | /// show only the names of the exercises
118 | names: bool,
119 | #[argh(option, short = 'f')]
120 | /// provide a string to match exercise names
121 | /// comma separated patterns are acceptable
122 | filter: Option,
123 | #[argh(switch, short = 'u')]
124 | /// display only exercises not yet solved
125 | unsolved: bool,
126 | #[argh(switch, short = 's')]
127 | /// display only exercises that have been solved
128 | solved: bool,
129 | }
130 |
131 | #[derive(Deserialize, Serialize)]
132 | pub struct ExerciseCheckList {
133 | pub exercises: Vec,
134 | pub user_name: Option,
135 | pub statistics: ExerciseStatistics
136 | }
137 |
138 | #[derive(Deserialize, Serialize)]
139 | pub struct ExerciseResult {
140 | pub name: String,
141 | pub result: bool
142 | }
143 |
144 | #[derive(Deserialize, Serialize)]
145 | pub struct ExerciseStatistics {
146 | pub total_exercations: usize,
147 | pub total_succeeds: usize,
148 | pub total_failures: usize,
149 | pub total_time: u32,
150 | }
151 |
152 | #[tokio::main]
153 | async fn main() {
154 | let args: Args = argh::from_env();
155 |
156 | if args.version {
157 | println!("v{VERSION}");
158 | std::process::exit(0);
159 | }
160 |
161 | if args.nested.is_none() {
162 | println!("\n{WELCOME}\n");
163 | }
164 |
165 | if !Path::new("info.toml").exists() {
166 | println!(
167 | "{} must be run from the rustlings directory",
168 | std::env::current_exe().unwrap().to_str().unwrap()
169 | );
170 | println!("Try `cd rustlings/`!");
171 | std::process::exit(1);
172 | }
173 |
174 | if !rustc_exists() {
175 | println!("We cannot find `rustc`.");
176 | println!("Try running `rustc --version` to diagnose your problem.");
177 | println!("For instructions on how to install Rust, check the README.");
178 | std::process::exit(1);
179 | }
180 |
181 | let toml_str = &fs::read_to_string("info.toml").unwrap();
182 | let exercises = toml::from_str::(toml_str).unwrap().exercises;
183 | let verbose = args.nocapture;
184 |
185 | let command = args.nested.unwrap_or_else(|| {
186 | println!("{DEFAULT_OUT}\n");
187 | std::process::exit(0);
188 | });
189 | match command {
190 | Subcommands::List(subargs) => {
191 | if !subargs.paths && !subargs.names {
192 | println!("{:<17}\t{:<46}\t{:<7}", "Name", "Path", "Status");
193 | }
194 | let mut exercises_done: u16 = 0;
195 | let filters = subargs.filter.clone().unwrap_or_default().to_lowercase();
196 | exercises.iter().for_each(|e| {
197 | let fname = format!("{}", e.path.display());
198 | let filter_cond = filters
199 | .split(',')
200 | .filter(|f| !f.trim().is_empty())
201 | .any(|f| e.name.contains(&f) || fname.contains(&f));
202 | let status = if e.looks_done() {
203 | exercises_done += 1;
204 | "Done"
205 | } else {
206 | "Pending"
207 | };
208 | let solve_cond = {
209 | (e.looks_done() && subargs.solved)
210 | || (!e.looks_done() && subargs.unsolved)
211 | || (!subargs.solved && !subargs.unsolved)
212 | };
213 | if solve_cond && (filter_cond || subargs.filter.is_none()) {
214 | let line = if subargs.paths {
215 | format!("{fname}\n")
216 | } else if subargs.names {
217 | format!("{}\n", e.name)
218 | } else {
219 | format!("{:<17}\t{fname:<46}\t{status:<7}\n", e.name)
220 | };
221 | // Somehow using println! leads to the binary panicking
222 | // when its output is piped.
223 | // So, we're handling a Broken Pipe error and exiting with 0 anyway
224 | let stdout = std::io::stdout();
225 | {
226 | let mut handle = stdout.lock();
227 | handle.write_all(line.as_bytes()).unwrap_or_else(|e| {
228 | match e.kind() {
229 | std::io::ErrorKind::BrokenPipe => std::process::exit(0),
230 | _ => std::process::exit(1),
231 | };
232 | });
233 | }
234 | }
235 | });
236 | let percentage_progress = exercises_done as f32 / exercises.len() as f32 * 100.0;
237 | println!(
238 | "Progress: You completed {} / {} exercises ({:.1} %).",
239 | exercises_done,
240 | exercises.len(),
241 | percentage_progress
242 | );
243 | std::process::exit(0);
244 | }
245 |
246 | Subcommands::Run(subargs) => {
247 | let exercise = find_exercise(&subargs.name, &exercises);
248 | run(exercise, verbose).unwrap_or_else(|_| std::process::exit(1));
249 | }
250 |
251 | Subcommands::Reset(subargs) => {
252 | let exercise = find_exercise(&subargs.name, &exercises);
253 |
254 | reset(exercise).unwrap_or_else(|_| std::process::exit(1));
255 | }
256 |
257 | Subcommands::Hint(subargs) => {
258 | let exercise = find_exercise(&subargs.name, &exercises);
259 |
260 | println!("{}", exercise.hint);
261 | }
262 |
263 | Subcommands::Verify(_subargs) => {
264 | verify(&exercises, (0, exercises.len()), verbose, false)
265 | .unwrap_or_else(|_| std::process::exit(1));
266 | }
267 |
268 | Subcommands::CicvVerify(_subargs) => {
269 | // let toml_str = &fs::read_to_string("info.toml").unwrap();
270 | // exercises = toml::from_str::(toml_str).unwrap().exercises;
271 | let now_start = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs();
272 | let rights = Arc::new(Mutex::new(0));
273 | let alls = exercises.len();
274 |
275 | let exercise_check_list = Arc::new(Mutex::new(
276 | ExerciseCheckList {
277 | exercises: vec![],
278 | user_name: None,
279 | statistics: ExerciseStatistics {
280 | total_exercations: alls,
281 | total_succeeds: 0,
282 | total_failures: 0,
283 | total_time: 0,
284 | }
285 | }
286 | ));
287 |
288 | let mut tasks = vec![];
289 | for exercise in exercises {
290 | let now_start = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs();
291 | let inner_exercise = exercise;
292 | let c_mutex = Arc::clone(&rights);
293 | let exercise_check_list_ref = Arc::clone(&exercise_check_list);
294 | let _verbose = verbose.clone();
295 | let t = tokio::task::spawn( async move {
296 | match run(&inner_exercise, true) {
297 | // match verify(vec![&inner_exercise], (0, 1), true, true) {
298 | Ok(_) => {
299 | *c_mutex.lock().unwrap() += 1;
300 | println!("{}执行成功", inner_exercise.name);
301 | println!("总的题目数: {}", alls);
302 | println!("当前做正确的题目数: {}", *c_mutex.lock().unwrap());
303 | let now_end = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs();
304 | println!("当前修改试卷耗时: {} s", now_end - now_start);
305 | exercise_check_list_ref.lock().unwrap().exercises.push(ExerciseResult{
306 | name: inner_exercise.name, result: true,
307 | });
308 | exercise_check_list_ref.lock().unwrap().statistics.total_succeeds += 1;
309 | },
310 | Err(_) => {
311 | println!("{}执行失败", inner_exercise.name);
312 | println!("总的题目数: {}", alls);
313 | println!("当前做正确的题目数: {}", *c_mutex.lock().unwrap());
314 | let now_end = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs();
315 | println!("当前修改试卷耗时: {} s", now_end - now_start);
316 | exercise_check_list_ref.lock().unwrap().exercises.push(ExerciseResult{
317 | name: inner_exercise.name, result: false,
318 | });
319 | exercise_check_list_ref.lock().unwrap().statistics.total_failures += 1;
320 | }
321 | }
322 | });
323 | tasks.push(t);
324 | }
325 | for task in tasks { task.await.unwrap(); }
326 | let now_end = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs();
327 | let total_time = now_end - now_start;
328 | println!("===============================试卷批改完成,总耗时: {} s; ==================================", total_time);
329 | let exercise_check_list_ref = Arc::clone(&exercise_check_list);
330 | exercise_check_list_ref.lock().unwrap().statistics.total_time = total_time as u32;
331 | let serialized = serde_json::to_string_pretty(&*exercise_check_list.lock().unwrap()).unwrap();
332 | fs::write(".github/result/check_result.json", serialized).unwrap();
333 | },
334 |
335 | Subcommands::Lsp(_subargs) => {
336 | let mut project = RustAnalyzerProject::new();
337 | project
338 | .get_sysroot_src()
339 | .expect("Couldn't find toolchain path, do you have `rustc` installed?");
340 | project
341 | .exercises_to_json()
342 | .expect("Couldn't parse rustlings exercises files");
343 |
344 | if project.crates.is_empty() {
345 | println!("Failed find any exercises, make sure you're in the `rustlings` folder");
346 | } else if project.write_to_disk().is_err() {
347 | println!("Failed to write rust-project.json to disk for rust-analyzer");
348 | } else {
349 | println!("Successfully generated rust-project.json");
350 | println!("rust-analyzer will now parse exercises, restart your language server or editor")
351 | }
352 | }
353 |
354 | Subcommands::Watch(_subargs) => match watch(&exercises, verbose, _subargs.success_hints) {
355 | Err(e) => {
356 | println!(
357 | "Error: Could not watch your progress. Error message was {:?}.",
358 | e
359 | );
360 | println!("Most likely you've run out of disk space or your 'inotify limit' has been reached.");
361 | std::process::exit(1);
362 | }
363 | Ok(WatchStatus::Finished) => {
364 | println!(
365 | "{emoji} All exercises completed! {emoji}",
366 | emoji = Emoji("🎉", "★")
367 | );
368 | println!("\n{FENISH_LINE}\n");
369 | }
370 | Ok(WatchStatus::Unfinished) => {
371 | println!("We hope you're enjoying learning about Rust!");
372 | println!("If you want to continue working on the exercises at a later point, you can simply run `rustlings watch` again");
373 | }
374 | },
375 | }
376 | }
377 |
378 | fn spawn_watch_shell(
379 | failed_exercise_hint: &Arc>>,
380 | should_quit: Arc,
381 | ) {
382 | let failed_exercise_hint = Arc::clone(failed_exercise_hint);
383 | println!("Welcome to watch mode! You can type 'help' to get an overview of the commands you can use here.");
384 | thread::spawn(move || loop {
385 | let mut input = String::new();
386 | match io::stdin().read_line(&mut input) {
387 | Ok(_) => {
388 | let input = input.trim();
389 | if input == "hint" {
390 | if let Some(hint) = &*failed_exercise_hint.lock().unwrap() {
391 | println!("{hint}");
392 | }
393 | } else if input == "clear" {
394 | println!("\x1B[2J\x1B[1;1H");
395 | } else if input.eq("quit") {
396 | should_quit.store(true, Ordering::SeqCst);
397 | println!("Bye!");
398 | } else if input.eq("help") {
399 | println!("Commands available to you in watch mode:");
400 | println!(" hint - prints the current exercise's hint");
401 | println!(" clear - clears the screen");
402 | println!(" quit - quits watch mode");
403 | println!(" ! - executes a command, like `!rustc --explain E0381`");
404 | println!(" help - displays this help message");
405 | println!();
406 | println!("Watch mode automatically re-evaluates the current exercise");
407 | println!("when you edit a file's contents.")
408 | } else if let Some(cmd) = input.strip_prefix('!') {
409 | let parts: Vec<&str> = cmd.split_whitespace().collect();
410 | if parts.is_empty() {
411 | println!("no command provided");
412 | } else if let Err(e) = Command::new(parts[0]).args(&parts[1..]).status() {
413 | println!("failed to execute command `{}`: {}", cmd, e);
414 | }
415 | } else {
416 | println!("unknown command: {input}");
417 | }
418 | }
419 | Err(error) => println!("error reading command: {error}"),
420 | }
421 | });
422 | }
423 |
424 | fn find_exercise<'a>(name: &str, exercises: &'a [Exercise]) -> &'a Exercise {
425 | if name.eq("next") {
426 | exercises
427 | .iter()
428 | .find(|e| !e.looks_done())
429 | .unwrap_or_else(|| {
430 | println!("🎉 Congratulations! You have done all the exercises!");
431 | println!("🔚 There are no more exercises to do next!");
432 | std::process::exit(1)
433 | })
434 | } else {
435 | exercises
436 | .iter()
437 | .find(|e| e.name == name)
438 | .unwrap_or_else(|| {
439 | println!("No exercise found for '{name}'!");
440 | std::process::exit(1)
441 | })
442 | }
443 | }
444 |
445 | enum WatchStatus {
446 | Finished,
447 | Unfinished,
448 | }
449 |
450 | fn watch(
451 | exercises: &[Exercise],
452 | verbose: bool,
453 | success_hints: bool,
454 | ) -> notify::Result {
455 | /* Clears the terminal with an ANSI escape code.
456 | Works in UNIX and newer Windows terminals. */
457 | fn clear_screen() {
458 | println!("\x1Bc");
459 | }
460 |
461 | let (tx, rx) = channel();
462 | let should_quit = Arc::new(AtomicBool::new(false));
463 |
464 | let mut watcher: RecommendedWatcher = Watcher::new(tx, Duration::from_secs(1))?;
465 | watcher.watch(Path::new("./exercises"), RecursiveMode::Recursive)?;
466 |
467 | clear_screen();
468 |
469 | let to_owned_hint = |t: &Exercise| t.hint.to_owned();
470 | let failed_exercise_hint = match verify(
471 | exercises.iter(),
472 | (0, exercises.len()),
473 | verbose,
474 | success_hints,
475 | ) {
476 | Ok(_) => return Ok(WatchStatus::Finished),
477 | Err(exercise) => Arc::new(Mutex::new(Some(to_owned_hint(exercise)))),
478 | };
479 | spawn_watch_shell(&failed_exercise_hint, Arc::clone(&should_quit));
480 | loop {
481 | match rx.recv_timeout(Duration::from_secs(1)) {
482 | Ok(event) => match event {
483 | DebouncedEvent::Create(b) | DebouncedEvent::Chmod(b) | DebouncedEvent::Write(b) => {
484 | if b.extension() == Some(OsStr::new("rs")) && b.exists() {
485 | let filepath = b.as_path().canonicalize().unwrap();
486 | let pending_exercises = exercises
487 | .iter()
488 | .find(|e| filepath.ends_with(&e.path))
489 | .into_iter()
490 | .chain(
491 | exercises
492 | .iter()
493 | .filter(|e| !e.looks_done() && !filepath.ends_with(&e.path)),
494 | );
495 | let num_done = exercises.iter().filter(|e| e.looks_done()).count();
496 | clear_screen();
497 | match verify(
498 | pending_exercises,
499 | (num_done, exercises.len()),
500 | verbose,
501 | success_hints,
502 | ) {
503 | Ok(_) => return Ok(WatchStatus::Finished),
504 | Err(exercise) => {
505 | let mut failed_exercise_hint = failed_exercise_hint.lock().unwrap();
506 | *failed_exercise_hint = Some(to_owned_hint(exercise));
507 | }
508 | }
509 | }
510 | }
511 | _ => {}
512 | },
513 | Err(RecvTimeoutError::Timeout) => {
514 | // the timeout expired, just check the `should_quit` variable below then loop again
515 | }
516 | Err(e) => println!("watch error: {e:?}"),
517 | }
518 | // Check if we need to exit
519 | if should_quit.load(Ordering::SeqCst) {
520 | return Ok(WatchStatus::Unfinished);
521 | }
522 | }
523 | }
524 |
525 | fn rustc_exists() -> bool {
526 | Command::new("rustc")
527 | .args(&["--version"])
528 | .stdout(Stdio::null())
529 | .spawn()
530 | .and_then(|mut child| child.wait())
531 | .map(|status| status.success())
532 | .unwrap_or(false)
533 | }
534 |
535 | const DEFAULT_OUT: &str = r#"Thanks for installing Rustlings!
536 |
537 | Is this your first time? Don't worry, Rustlings was made for beginners! We are
538 | going to teach you a lot of things about Rust, but before we can get
539 | started, here's a couple of notes about how Rustlings operates:
540 |
541 | 1. The central concept behind Rustlings is that you solve exercises. These
542 | exercises usually have some sort of syntax error in them, which will cause
543 | them to fail compilation or testing. Sometimes there's a logic error instead
544 | of a syntax error. No matter what error, it's your job to find it and fix it!
545 | You'll know when you fixed it because then, the exercise will compile and
546 | Rustlings will be able to move on to the next exercise.
547 | 2. If you run Rustlings in watch mode (which we recommend), it'll automatically
548 | start with the first exercise. Don't get confused by an error message popping
549 | up as soon as you run Rustlings! This is part of the exercise that you're
550 | supposed to solve, so open the exercise file in an editor and start your
551 | detective work!
552 | 3. If you're stuck on an exercise, there is a helpful hint you can view by typing
553 | 'hint' (in watch mode), or running `rustlings hint exercise_name`.
554 | 4. If an exercise doesn't make sense to you, feel free to open an issue on GitHub!
555 | (https://github.com/rust-lang/rustlings/issues/new). We look at every issue,
556 | and sometimes, other learners do too so you can help each other out!
557 | 5. If you want to use `rust-analyzer` with exercises, which provides features like
558 | autocompletion, run the command `rustlings lsp`.
559 |
560 | Got all that? Great! To get started, run `rustlings watch` in order to get the first
561 | exercise. Make sure to have your editor open!"#;
562 |
563 | const FENISH_LINE: &str = r#"+----------------------------------------------------+
564 | | You made it to the Fe-nish line! |
565 | +-------------------------- ------------------------+
566 | \\/
567 | ▒▒ ▒▒▒▒▒▒▒▒ ▒▒▒▒▒▒▒▒ ▒▒
568 | ▒▒▒▒ ▒▒ ▒▒ ▒▒ ▒▒ ▒▒ ▒▒ ▒▒▒▒
569 | ▒▒▒▒ ▒▒ ▒▒ ▒▒ ▒▒ ▒▒ ▒▒▒▒
570 | ░░▒▒▒▒░░▒▒ ▒▒ ▒▒ ▒▒ ▒▒░░▒▒▒▒
571 | ▓▓▓▓▓▓▓▓ ▓▓ ▓▓██ ▓▓ ▓▓██ ▓▓ ▓▓▓▓▓▓▓▓
572 | ▒▒▒▒ ▒▒ ████ ▒▒ ████ ▒▒░░ ▒▒▒▒
573 | ▒▒ ▒▒▒▒▒▒ ▒▒▒▒▒▒ ▒▒▒▒▒▒ ▒▒
574 | ▒▒▒▒▒▒▒▒▒▒▓▓▓▓▓▓▒▒▒▒▒▒▒▒▓▓▒▒▓▓▒▒▒▒▒▒▒▒
575 | ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒
576 | ▒▒▒▒▒▒▒▒▒▒██▒▒▒▒▒▒██▒▒▒▒▒▒▒▒▒▒
577 | ▒▒ ▒▒▒▒▒▒▒▒▒▒██████▒▒▒▒▒▒▒▒▒▒ ▒▒
578 | ▒▒ ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒ ▒▒
579 | ▒▒ ▒▒ ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒ ▒▒ ▒▒
580 | ▒▒ ▒▒ ▒▒ ▒▒ ▒▒ ▒▒
581 | ▒▒ ▒▒ ▒▒ ▒▒
582 |
583 | We hope you enjoyed learning about the various aspects of Rust!
584 | If you noticed any issues, please don't hesitate to report them to our repo.
585 | You can also contribute your own exercises to help the greater community!
586 |
587 | Before reporting an issue or contributing, please read our guidelines:
588 | https://github.com/rust-lang/rustlings/blob/main/CONTRIBUTING.md"#;
589 |
590 | const WELCOME: &str = r#" welcome to...
591 | _ _ _
592 | _ __ _ _ ___| |_| (_)_ __ __ _ ___
593 | | '__| | | / __| __| | | '_ \ / _` / __|
594 | | | | |_| \__ \ |_| | | | | | (_| \__ \
595 | |_| \__,_|___/\__|_|_|_| |_|\__, |___/
596 | |___/"#;
597 |
--------------------------------------------------------------------------------
/src/project.rs:
--------------------------------------------------------------------------------
1 | use glob::glob;
2 | use serde::{Deserialize, Serialize};
3 | use std::env;
4 | use std::error::Error;
5 | use std::path::PathBuf;
6 | use std::process::Command;
7 |
8 | /// Contains the structure of resulting rust-project.json file
9 | /// and functions to build the data required to create the file
10 | #[derive(Serialize, Deserialize)]
11 | pub struct RustAnalyzerProject {
12 | sysroot_src: String,
13 | pub crates: Vec,
14 | }
15 |
16 | #[derive(Serialize, Deserialize)]
17 | pub struct Crate {
18 | root_module: String,
19 | edition: String,
20 | deps: Vec,
21 | cfg: Vec,
22 | }
23 |
24 | impl RustAnalyzerProject {
25 | pub fn new() -> RustAnalyzerProject {
26 | RustAnalyzerProject {
27 | sysroot_src: String::new(),
28 | crates: Vec::new(),
29 | }
30 | }
31 |
32 | /// Write rust-project.json to disk
33 | pub fn write_to_disk(&self) -> Result<(), std::io::Error> {
34 | std::fs::write(
35 | "./rust-project.json",
36 | serde_json::to_vec(&self).expect("Failed to serialize to JSON"),
37 | )?;
38 | Ok(())
39 | }
40 |
41 | /// If path contains .rs extension, add a crate to `rust-project.json`
42 | fn path_to_json(&mut self, path: PathBuf) -> Result<(), Box> {
43 | if let Some(ext) = path.extension() {
44 | if ext == "rs" {
45 | self.crates.push(Crate {
46 | root_module: path.display().to_string(),
47 | edition: "2021".to_string(),
48 | deps: Vec::new(),
49 | // This allows rust_analyzer to work inside #[test] blocks
50 | cfg: vec!["test".to_string()],
51 | })
52 | }
53 | }
54 |
55 | Ok(())
56 | }
57 |
58 | /// Parse the exercises folder for .rs files, any matches will create
59 | /// a new `crate` in rust-project.json which allows rust-analyzer to
60 | /// treat it like a normal binary
61 | pub fn exercises_to_json(&mut self) -> Result<(), Box> {
62 | for path in glob("./exercises/**/*")? {
63 | self.path_to_json(path?)?;
64 | }
65 | Ok(())
66 | }
67 |
68 | /// Use `rustc` to determine the default toolchain
69 | pub fn get_sysroot_src(&mut self) -> Result<(), Box> {
70 | // check if RUST_SRC_PATH is set
71 | if let Ok(path) = env::var("RUST_SRC_PATH") {
72 | self.sysroot_src = path;
73 | return Ok(());
74 | }
75 |
76 | let toolchain = Command::new("rustc")
77 | .arg("--print")
78 | .arg("sysroot")
79 | .output()?
80 | .stdout;
81 |
82 | let toolchain = String::from_utf8_lossy(&toolchain);
83 | let mut whitespace_iter = toolchain.split_whitespace();
84 |
85 | let toolchain = whitespace_iter.next().unwrap_or(&toolchain);
86 |
87 | println!("Determined toolchain: {}\n", &toolchain);
88 |
89 | self.sysroot_src = (std::path::Path::new(&*toolchain)
90 | .join("lib")
91 | .join("rustlib")
92 | .join("src")
93 | .join("rust")
94 | .join("library")
95 | .to_string_lossy())
96 | .to_string();
97 | Ok(())
98 | }
99 | }
100 |
--------------------------------------------------------------------------------
/src/run.rs:
--------------------------------------------------------------------------------
1 | use std::process::Command;
2 |
3 | use crate::exercise::{Exercise, Mode};
4 | use crate::verify::test;
5 | use indicatif::ProgressBar;
6 |
7 | // Invoke the rust compiler on the path of the given exercise,
8 | // and run the ensuing binary.
9 | // The verbose argument helps determine whether or not to show
10 | // the output from the test harnesses (if the mode of the exercise is test)
11 | pub fn run(exercise: &Exercise, verbose: bool) -> Result<(), ()> {
12 | match exercise.mode {
13 | Mode::Test => test(exercise, verbose)?,
14 | Mode::Compile => compile_and_run(exercise)?,
15 | Mode::Clippy => compile_and_run(exercise)?,
16 | Mode::BuildScript => test(exercise, verbose)?,
17 | }
18 | Ok(())
19 | }
20 |
21 | // Resets the exercise by stashing the changes.
22 | pub fn reset(exercise: &Exercise) -> Result<(), ()> {
23 | let command = Command::new("git")
24 | .args(["stash", "--"])
25 | .arg(&exercise.path)
26 | .spawn();
27 |
28 | match command {
29 | Ok(_) => Ok(()),
30 | Err(_) => Err(()),
31 | }
32 | }
33 |
34 | // Invoke the rust compiler on the path of the given exercise
35 | // and run the ensuing binary.
36 | // This is strictly for non-test binaries, so output is displayed
37 | fn compile_and_run(exercise: &Exercise) -> Result<(), ()> {
38 | let progress_bar = ProgressBar::new_spinner();
39 | progress_bar.set_message(format!("Compiling {exercise}..."));
40 | progress_bar.enable_steady_tick(100);
41 |
42 | let compilation_result = exercise.compile();
43 | let compilation = match compilation_result {
44 | Ok(compilation) => compilation,
45 | Err(output) => {
46 | progress_bar.finish_and_clear();
47 | warn!(
48 | "Compilation of {} failed!, Compiler error message:\n",
49 | exercise
50 | );
51 | println!("{}", output.stderr);
52 | return Err(());
53 | }
54 | };
55 |
56 | progress_bar.set_message(format!("Running {exercise}..."));
57 | let result = compilation.run();
58 | progress_bar.finish_and_clear();
59 |
60 | match result {
61 | Ok(output) => {
62 | println!("{}", output.stdout);
63 | success!("Successfully ran {}", exercise);
64 | Ok(())
65 | }
66 | Err(output) => {
67 | println!("{}", output.stdout);
68 | println!("{}", output.stderr);
69 |
70 | warn!("Ran {} with errors", exercise);
71 | Err(())
72 | }
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/src/ui.rs:
--------------------------------------------------------------------------------
1 | macro_rules! warn {
2 | ($fmt:literal, $ex:expr) => {{
3 | use console::{style, Emoji};
4 | use std::env;
5 | let formatstr = format!($fmt, $ex);
6 | if env::var("NO_EMOJI").is_ok() {
7 | println!("{} {}", style("!").red(), style(formatstr).red());
8 | } else {
9 | println!(
10 | "{} {}",
11 | style(Emoji("⚠️ ", "!")).red(),
12 | style(formatstr).red()
13 | );
14 | }
15 | }};
16 | }
17 |
18 | macro_rules! success {
19 | ($fmt:literal, $ex:expr) => {{
20 | use console::{style, Emoji};
21 | use std::env;
22 | let formatstr = format!($fmt, $ex);
23 | if env::var("NO_EMOJI").is_ok() {
24 | println!("{} {}", style("✓").green(), style(formatstr).green());
25 | } else {
26 | println!(
27 | "{} {}",
28 | style(Emoji("✅", "✓")).green(),
29 | style(formatstr).green()
30 | );
31 | }
32 | }};
33 | }
34 |
--------------------------------------------------------------------------------
/src/verify.rs:
--------------------------------------------------------------------------------
1 | use crate::exercise::{CompiledExercise, Exercise, Mode, State};
2 | use console::style;
3 | use indicatif::{ProgressBar, ProgressStyle};
4 | use std::env;
5 |
6 | // Verify that the provided container of Exercise objects
7 | // can be compiled and run without any failures.
8 | // Any such failures will be reported to the end user.
9 | // If the Exercise being verified is a test, the verbose boolean
10 | // determines whether or not the test harness outputs are displayed.
11 | pub fn verify<'a>(
12 | exercises: impl IntoIterator- ,
13 | progress: (usize, usize),
14 | verbose: bool,
15 | success_hints: bool,
16 | ) -> Result<(), &'a Exercise> {
17 | let (num_done, total) = progress;
18 | let bar = ProgressBar::new(total as u64);
19 | let mut percentage = num_done as f32 / total as f32 * 100.0;
20 | bar.set_style(ProgressStyle::default_bar()
21 | .template("Progress: [{bar:60.green/red}] {pos}/{len} {msg}")
22 | .progress_chars("#>-")
23 | );
24 | bar.set_position(num_done as u64);
25 | bar.set_message(format!("({:.1} %)", percentage));
26 |
27 | for exercise in exercises {
28 | let compile_result = match exercise.mode {
29 | Mode::Test => compile_and_test(exercise, RunMode::Interactive, verbose, success_hints),
30 | Mode::Compile => compile_and_run_interactively(exercise, success_hints),
31 | Mode::Clippy => compile_only(exercise, success_hints),
32 | Mode::BuildScript => compile_and_test(exercise, RunMode::Interactive, verbose, success_hints),
33 |
34 | };
35 | if !compile_result.unwrap_or(false) {
36 | return Err(exercise);
37 | }
38 | percentage += 100.0 / total as f32;
39 | bar.inc(1);
40 | bar.set_message(format!("({:.1} %)", percentage));
41 | }
42 | Ok(())
43 | }
44 |
45 | enum RunMode {
46 | Interactive,
47 | NonInteractive,
48 | }
49 |
50 | // Compile and run the resulting test harness of the given Exercise
51 | pub fn test(exercise: &Exercise, verbose: bool) -> Result<(), ()> {
52 | compile_and_test(exercise, RunMode::NonInteractive, verbose, false)?;
53 | Ok(())
54 | }
55 |
56 | // Invoke the rust compiler without running the resulting binary
57 | fn compile_only(exercise: &Exercise, success_hints: bool) -> Result {
58 | let progress_bar = ProgressBar::new_spinner();
59 | progress_bar.set_message(format!("Compiling {exercise}..."));
60 | progress_bar.enable_steady_tick(100);
61 |
62 | let _ = compile(exercise, &progress_bar)?;
63 | progress_bar.finish_and_clear();
64 |
65 | Ok(prompt_for_completion(exercise, None, success_hints))
66 | }
67 |
68 | // Compile the given Exercise and run the resulting binary in an interactive mode
69 | fn compile_and_run_interactively(exercise: &Exercise, success_hints: bool) -> Result {
70 | let progress_bar = ProgressBar::new_spinner();
71 | progress_bar.set_message(format!("Compiling {exercise}..."));
72 | progress_bar.enable_steady_tick(100);
73 |
74 | let compilation = compile(exercise, &progress_bar)?;
75 |
76 | progress_bar.set_message(format!("Running {exercise}..."));
77 | let result = compilation.run();
78 | progress_bar.finish_and_clear();
79 |
80 | let output = match result {
81 | Ok(output) => output,
82 | Err(output) => {
83 | warn!("Ran {} with errors", exercise);
84 | println!("{}", output.stdout);
85 | println!("{}", output.stderr);
86 | return Err(());
87 | }
88 | };
89 |
90 | Ok(prompt_for_completion(exercise, Some(output.stdout), success_hints))
91 | }
92 |
93 | // Compile the given Exercise as a test harness and display
94 | // the output if verbose is set to true
95 | fn compile_and_test(exercise: &Exercise, run_mode: RunMode, verbose: bool, success_hints: bool) -> Result {
96 | let progress_bar = ProgressBar::new_spinner();
97 | progress_bar.set_message(format!("Testing {exercise}..."));
98 | progress_bar.enable_steady_tick(100);
99 |
100 | let compilation = compile(exercise, &progress_bar)?;
101 | let result = compilation.run();
102 | progress_bar.finish_and_clear();
103 |
104 | match result {
105 | Ok(output) => {
106 | if verbose {
107 | println!("{}", output.stdout);
108 | }
109 | if let RunMode::Interactive = run_mode {
110 | Ok(prompt_for_completion(exercise, None, success_hints))
111 | } else {
112 | Ok(true)
113 | }
114 | }
115 | Err(output) => {
116 | warn!(
117 | "Testing of {} failed! Please try again. Here's the output:",
118 | exercise
119 | );
120 | println!("{}", output.stdout);
121 | Err(())
122 | }
123 | }
124 | }
125 |
126 | // Compile the given Exercise and return an object with information
127 | // about the state of the compilation
128 | fn compile<'a, 'b>(
129 | exercise: &'a Exercise,
130 | progress_bar: &'b ProgressBar,
131 | ) -> Result, ()> {
132 | let compilation_result = exercise.compile();
133 |
134 | match compilation_result {
135 | Ok(compilation) => Ok(compilation),
136 | Err(output) => {
137 | progress_bar.finish_and_clear();
138 | warn!(
139 | "Compiling of {} failed! Please try again. Here's the output:",
140 | exercise
141 | );
142 | println!("{}", output.stderr);
143 | Err(())
144 | }
145 | }
146 | }
147 |
148 | fn prompt_for_completion(exercise: &Exercise, prompt_output: Option, success_hints: bool) -> bool {
149 | let context = match exercise.state() {
150 | State::Done => return true,
151 | State::Pending(context) => context,
152 | };
153 | match exercise.mode {
154 | Mode::Compile => success!("Successfully ran {}!", exercise),
155 | Mode::Test => success!("Successfully tested {}!", exercise),
156 | Mode::Clippy => success!("Successfully compiled {}!", exercise),
157 | Mode::BuildScript => success!("Successfully compiled {}!", exercise),
158 | }
159 |
160 | let no_emoji = env::var("NO_EMOJI").is_ok();
161 |
162 | let clippy_success_msg = if no_emoji {
163 | "The code is compiling, and Clippy is happy!"
164 | } else {
165 | "The code is compiling, and 📎 Clippy 📎 is happy!"
166 | };
167 |
168 | let success_msg = match exercise.mode {
169 | Mode::Compile => "The code is compiling!",
170 | Mode::Test => "The code is compiling, and the tests pass!",
171 | Mode::Clippy => clippy_success_msg,
172 | Mode::BuildScript => "Build script works!",
173 | };
174 | println!();
175 | if no_emoji {
176 | println!("~*~ {success_msg} ~*~")
177 | } else {
178 | println!("🎉 🎉 {success_msg} 🎉 🎉")
179 | }
180 | println!();
181 |
182 | if let Some(output) = prompt_output {
183 | println!("Output:");
184 | println!("{}", separator());
185 | println!("{output}");
186 | println!("{}", separator());
187 | println!();
188 | }
189 | if success_hints {
190 | println!("Hints:");
191 | println!("{}", separator());
192 | println!("{}", exercise.hint);
193 | println!("{}", separator());
194 | println!();
195 | }
196 |
197 | println!("You can keep working on this exercise,");
198 | println!(
199 | "or jump into the next one by removing the {} comment:",
200 | style("`I AM NOT DONE`").bold()
201 | );
202 | println!();
203 | for context_line in context {
204 | let formatted_line = if context_line.important {
205 | format!("{}", style(context_line.line).bold())
206 | } else {
207 | context_line.line.to_string()
208 | };
209 |
210 | println!(
211 | "{:>2} {} {}",
212 | style(context_line.number).blue().bold(),
213 | style("|").blue(),
214 | formatted_line
215 | );
216 | }
217 |
218 | false
219 | }
220 |
221 | fn separator() -> console::StyledObject<&'static str> {
222 | style("====================").bold()
223 | }
224 |
--------------------------------------------------------------------------------
/tests/cicv.rs:
--------------------------------------------------------------------------------
1 | use assert_cmd::prelude::*;
2 | use std::process::Command;
3 |
4 | #[test]
5 | fn cicvverify() {
6 | Command::cargo_bin("rustlings")
7 | .unwrap()
8 | .args(&["--nocapture", "cicvverify"])
9 | // .current_dir("exercises")
10 | .assert()
11 | .success();
12 | }
13 |
--------------------------------------------------------------------------------
/tests/fixture/failure/compFailure.rs:
--------------------------------------------------------------------------------
1 | fn main() {
2 | let
3 | }
--------------------------------------------------------------------------------
/tests/fixture/failure/compNoExercise.rs:
--------------------------------------------------------------------------------
1 | fn main() {
2 | }
3 |
--------------------------------------------------------------------------------
/tests/fixture/failure/info.toml:
--------------------------------------------------------------------------------
1 | [[exercises]]
2 | name = "compFailure"
3 | path = "compFailure.rs"
4 | mode = "compile"
5 | hint = ""
6 |
7 | [[exercises]]
8 | name = "testFailure"
9 | path = "testFailure.rs"
10 | mode = "test"
11 | hint = "Hello!"
12 |
--------------------------------------------------------------------------------
/tests/fixture/failure/testFailure.rs:
--------------------------------------------------------------------------------
1 | #[test]
2 | fn passing() {
3 | asset!(true);
4 | }
5 |
--------------------------------------------------------------------------------
/tests/fixture/failure/testNotPassed.rs:
--------------------------------------------------------------------------------
1 | #[test]
2 | fn not_passing() {
3 | assert!(false);
4 | }
5 |
--------------------------------------------------------------------------------
/tests/fixture/state/finished_exercise.rs:
--------------------------------------------------------------------------------
1 | // fake_exercise
2 |
3 | fn main() {
4 |
5 | }
6 |
--------------------------------------------------------------------------------
/tests/fixture/state/info.toml:
--------------------------------------------------------------------------------
1 | [[exercises]]
2 | name = "pending_exercise"
3 | path = "pending_exercise.rs"
4 | mode = "compile"
5 | hint = """"""
6 |
7 | [[exercises]]
8 | name = "pending_test_exercise"
9 | path = "pending_test_exercise.rs"
10 | mode = "test"
11 | hint = """"""
12 |
13 | [[exercises]]
14 | name = "finished_exercise"
15 | path = "finished_exercise.rs"
16 | mode = "compile"
17 | hint = """"""
18 |
19 |
--------------------------------------------------------------------------------
/tests/fixture/state/pending_exercise.rs:
--------------------------------------------------------------------------------
1 | // fake_exercise
2 |
3 | // I AM NOT DONE
4 |
5 | fn main() {
6 |
7 | }
8 |
--------------------------------------------------------------------------------
/tests/fixture/state/pending_test_exercise.rs:
--------------------------------------------------------------------------------
1 | // I AM NOT DONE
2 |
3 | #[test]
4 | fn it_works() {}
5 |
--------------------------------------------------------------------------------
/tests/fixture/success/compSuccess.rs:
--------------------------------------------------------------------------------
1 | fn main() {
2 | }
3 |
--------------------------------------------------------------------------------
/tests/fixture/success/info.toml:
--------------------------------------------------------------------------------
1 | [[exercises]]
2 | name = "compSuccess"
3 | path = "compSuccess.rs"
4 | mode = "compile"
5 | hint = """"""
6 |
7 | [[exercises]]
8 | name = "testSuccess"
9 | path = "testSuccess.rs"
10 | mode = "test"
11 | hint = """"""
12 |
--------------------------------------------------------------------------------
/tests/fixture/success/testSuccess.rs:
--------------------------------------------------------------------------------
1 | #[test]
2 | fn passing() {
3 | println!("THIS TEST TOO SHALL PASS");
4 | assert!(true);
5 | }
6 |
--------------------------------------------------------------------------------
/tests/integration_tests.rs:
--------------------------------------------------------------------------------
1 | use assert_cmd::prelude::*;
2 | use glob::glob;
3 | use predicates::boolean::PredicateBooleanExt;
4 | use std::fs::File;
5 | use std::io::Read;
6 | use std::process::Command;
7 |
8 | #[test]
9 | fn runs_without_arguments() {
10 | let mut cmd = Command::cargo_bin("rustlings").unwrap();
11 | cmd.assert().success();
12 | }
13 |
14 | #[test]
15 | fn fails_when_in_wrong_dir() {
16 | Command::cargo_bin("rustlings")
17 | .unwrap()
18 | .current_dir("tests/")
19 | .assert()
20 | .code(1);
21 | }
22 |
23 | #[test]
24 | fn verify_all_success() {
25 | Command::cargo_bin("rustlings")
26 | .unwrap()
27 | .arg("verify")
28 | .current_dir("tests/fixture/success")
29 | .assert()
30 | .success();
31 | }
32 |
33 | #[test]
34 | fn verify_fails_if_some_fails() {
35 | Command::cargo_bin("rustlings")
36 | .unwrap()
37 | .arg("verify")
38 | .current_dir("tests/fixture/failure")
39 | .assert()
40 | .code(1);
41 | }
42 |
43 | #[test]
44 | fn run_single_compile_success() {
45 | Command::cargo_bin("rustlings")
46 | .unwrap()
47 | .args(&["run", "compSuccess"])
48 | .current_dir("tests/fixture/success/")
49 | .assert()
50 | .success();
51 | }
52 |
53 | #[test]
54 | fn run_single_compile_failure() {
55 | Command::cargo_bin("rustlings")
56 | .unwrap()
57 | .args(&["run", "compFailure"])
58 | .current_dir("tests/fixture/failure/")
59 | .assert()
60 | .code(1);
61 | }
62 |
63 | #[test]
64 | fn run_single_test_success() {
65 | Command::cargo_bin("rustlings")
66 | .unwrap()
67 | .args(&["run", "testSuccess"])
68 | .current_dir("tests/fixture/success/")
69 | .assert()
70 | .success();
71 | }
72 |
73 | #[test]
74 | fn run_single_test_failure() {
75 | Command::cargo_bin("rustlings")
76 | .unwrap()
77 | .args(&["run", "testFailure"])
78 | .current_dir("tests/fixture/failure/")
79 | .assert()
80 | .code(1);
81 | }
82 |
83 | #[test]
84 | fn run_single_test_not_passed() {
85 | Command::cargo_bin("rustlings")
86 | .unwrap()
87 | .args(&["run", "testNotPassed.rs"])
88 | .current_dir("tests/fixture/failure/")
89 | .assert()
90 | .code(1);
91 | }
92 |
93 | #[test]
94 | fn run_single_test_no_filename() {
95 | Command::cargo_bin("rustlings")
96 | .unwrap()
97 | .arg("run")
98 | .current_dir("tests/fixture/")
99 | .assert()
100 | .code(1);
101 | }
102 |
103 | #[test]
104 | fn run_single_test_no_exercise() {
105 | Command::cargo_bin("rustlings")
106 | .unwrap()
107 | .args(&["run", "compNoExercise.rs"])
108 | .current_dir("tests/fixture/failure")
109 | .assert()
110 | .code(1);
111 | }
112 |
113 | #[test]
114 | fn reset_single_exercise() {
115 | Command::cargo_bin("rustlings")
116 | .unwrap()
117 | .args(&["reset", "intro1"])
118 | .assert()
119 | .code(0);
120 | }
121 |
122 | #[test]
123 | fn reset_no_exercise() {
124 | Command::cargo_bin("rustlings")
125 | .unwrap()
126 | .arg("reset")
127 | .assert()
128 | .code(1)
129 | .stderr(predicates::str::contains(
130 | "positional arguments not provided",
131 | ));
132 | }
133 |
134 | #[test]
135 | fn get_hint_for_single_test() {
136 | Command::cargo_bin("rustlings")
137 | .unwrap()
138 | .args(&["hint", "testFailure"])
139 | .current_dir("tests/fixture/failure")
140 | .assert()
141 | .code(0)
142 | .stdout("Hello!\n");
143 | }
144 |
145 | #[test]
146 | fn all_exercises_require_confirmation() {
147 | for exercise in glob("exercises/**/*.rs").unwrap() {
148 | let path = exercise.unwrap();
149 | if path.file_name().unwrap() == "mod.rs" {
150 | continue;
151 | }
152 | let source = {
153 | let mut file = File::open(&path).unwrap();
154 | let mut s = String::new();
155 | file.read_to_string(&mut s).unwrap();
156 | s
157 | };
158 | source
159 | .matches("// I AM NOT DONE")
160 | .next()
161 | .unwrap_or_else(|| {
162 | panic!(
163 | "There should be an `I AM NOT DONE` annotation in {:?}",
164 | path
165 | )
166 | });
167 | }
168 | }
169 |
170 | #[test]
171 | fn run_compile_exercise_does_not_prompt() {
172 | Command::cargo_bin("rustlings")
173 | .unwrap()
174 | .args(&["run", "pending_exercise"])
175 | .current_dir("tests/fixture/state")
176 | .assert()
177 | .code(0)
178 | .stdout(predicates::str::contains("I AM NOT DONE").not());
179 | }
180 |
181 | #[test]
182 | fn run_test_exercise_does_not_prompt() {
183 | Command::cargo_bin("rustlings")
184 | .unwrap()
185 | .args(&["run", "pending_test_exercise"])
186 | .current_dir("tests/fixture/state")
187 | .assert()
188 | .code(0)
189 | .stdout(predicates::str::contains("I AM NOT DONE").not());
190 | }
191 |
192 | #[test]
193 | fn run_single_test_success_with_output() {
194 | Command::cargo_bin("rustlings")
195 | .unwrap()
196 | .args(&["--nocapture", "run", "testSuccess"])
197 | .current_dir("tests/fixture/success/")
198 | .assert()
199 | .code(0)
200 | .stdout(predicates::str::contains("THIS TEST TOO SHALL PASS"));
201 | }
202 |
203 | #[test]
204 | fn run_single_test_success_without_output() {
205 | Command::cargo_bin("rustlings")
206 | .unwrap()
207 | .args(&["run", "testSuccess"])
208 | .current_dir("tests/fixture/success/")
209 | .assert()
210 | .code(0)
211 | .stdout(predicates::str::contains("THIS TEST TOO SHALL PASS").not());
212 | }
213 |
214 | #[test]
215 | fn run_rustlings_list() {
216 | Command::cargo_bin("rustlings")
217 | .unwrap()
218 | .args(&["list"])
219 | .current_dir("tests/fixture/success")
220 | .assert()
221 | .success();
222 | }
223 |
224 | #[test]
225 | fn run_rustlings_list_no_pending() {
226 | Command::cargo_bin("rustlings")
227 | .unwrap()
228 | .args(&["list"])
229 | .current_dir("tests/fixture/success")
230 | .assert()
231 | .success()
232 | .stdout(predicates::str::contains("Pending").not());
233 | }
234 |
235 | #[test]
236 | fn run_rustlings_list_both_done_and_pending() {
237 | Command::cargo_bin("rustlings")
238 | .unwrap()
239 | .args(&["list"])
240 | .current_dir("tests/fixture/state")
241 | .assert()
242 | .success()
243 | .stdout(predicates::str::contains("Done").and(predicates::str::contains("Pending")));
244 | }
245 |
246 | #[test]
247 | fn run_rustlings_list_without_pending() {
248 | Command::cargo_bin("rustlings")
249 | .unwrap()
250 | .args(&["list", "--solved"])
251 | .current_dir("tests/fixture/state")
252 | .assert()
253 | .success()
254 | .stdout(predicates::str::contains("Pending").not());
255 | }
256 |
257 | #[test]
258 | fn run_rustlings_list_without_done() {
259 | Command::cargo_bin("rustlings")
260 | .unwrap()
261 | .args(&["list", "--unsolved"])
262 | .current_dir("tests/fixture/state")
263 | .assert()
264 | .success()
265 | .stdout(predicates::str::contains("Done").not());
266 | }
267 |
--------------------------------------------------------------------------------