├── .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 | [![Open in Gitpod](https://gitpod.io/button/open-in-gitpod.svg)](https://gitpod.io/#https://github.com/rust-lang/rustlings) 98 | 99 | [![Open Rustlings On Codespaces](https://github.com/codespaces/badge.svg)](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 | --------------------------------------------------------------------------------