├── .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 ├── functions │ ├── README.md │ ├── functions1.rs │ ├── functions2.rs │ ├── functions3.rs │ ├── functions4.rs │ └── functions5.rs ├── if │ ├── README.md │ ├── if1.rs │ ├── if2.rs │ └── if3.rs ├── intro │ ├── README.md │ ├── intro1.rs │ └── intro2.rs ├── quiz1.rs └── variables │ ├── README.md │ ├── variables1.rs │ ├── variables2.rs │ ├── variables3.rs │ ├── variables4.rs │ ├── variables5.rs │ └── variables6.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: "N1ZkWlBScVpqUklOWElZUk1RZGtBZnZibDFPM1V1" 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 1527 \ 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 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | 2 | ## 5.5.1 (2023-05-17) 3 | 4 | #### Fixed 5 | 6 | - Reverted `rust-project.json` path generation due to an upstream `rust-analyzer` fix. 7 | 8 | 9 | ## 5.5.0 (2023-05-17) 10 | 11 | #### Added 12 | 13 | - `strings2`: Added a reference to the book chapter for reference conversion 14 | - `lifetimes`: Added a link to the lifetimekata project 15 | - Added a new `tests4` exercises, which teaches about testing for panics 16 | - Added a `!` prefix command to watch mode that runs an external command 17 | - Added a `--success-hints` option to watch mode that shows hints on exercise success 18 | 19 | #### Changed 20 | 21 | - `vecs2`: Renamed iterator variable bindings for clarify 22 | - `lifetimes`: Changed order of book references 23 | - `hashmaps2`: Clarified instructions in the todo block 24 | - Moved lifetime exercises before test exercises (via the recommended book ordering) 25 | - `options2`: Improved tests for layering options 26 | - `modules2`: Added more information to the hint 27 | 28 | #### Fixed 29 | 30 | - `errors2`: Corrected a comment wording 31 | - `iterators2`: Fixed a spelling mistake in the hint text 32 | - `variables`: Wrapped the mut keyword with backticks for readability 33 | - `move_semantics2`: Removed references to line numbers 34 | - `cow1`: Clarified the `owned_no_mutation` comments 35 | - `options3`: Changed exercise to panic when no match is found 36 | - `rustlings lsp` now generates absolute paths, which should fix VSCode `rust-analyzer` usage on Windows 37 | 38 | #### Housekeeping 39 | 40 | - Added a markdown linter to run on GitHub actions 41 | - Split quick installation section into two code blocks 42 | 43 | 44 | ## 5.4.1 (2023-03-10) 45 | 46 | #### Changed 47 | 48 | - `vecs`: Added links to `iter_mut` and `map` to README.md 49 | - `cow1`: Changed main to tests 50 | - `iterators1`: Formatted according to rustfmt 51 | 52 | #### Fixed 53 | 54 | - `errors5`: Unified undisclosed type notation 55 | - `arc1`: Improved readability by avoiding implicit dereference 56 | - `macros4`: Prevented auto-fix by adding `#[rustfmt::skip]` 57 | - `cli`: Actually show correct progress percentages 58 | 59 | 60 | 61 | ## 5.4.0 (2023-02-12) 62 | 63 | #### Changed 64 | 65 | - Reordered exercises 66 | - Unwrapped `standard_library_types` into `iterators` and `smart_pointers` 67 | - Moved smart pointer exercises behind threads 68 | - Ordered `rc1` before `arc1` 69 | - **intro1**: Added a note on `rustlings lsp` 70 | - **threads1**: Panic if threads are not joined 71 | - **cli**: 72 | - Made progress bar update proportional to amount of files verified 73 | - Decreased `watch` delay from 2 to 1 second 74 | 75 | #### Fixed 76 | 77 | - Capitalized "Rust" in exercise hints 78 | - **enums3**: Removed superfluous tuple brackets 79 | - **quiz2, clippy1, iterators1**: Fixed a typo 80 | - **rc1**: Fixed a prompt error 81 | - **cli**: 82 | - Fixed a typo in a method name 83 | - Specified the edition in `rustc` commands 84 | 85 | #### Housekeeping 86 | 87 | - Bumped min Rust version to 1.58 in installation script 88 | 89 | 90 | 91 | ## 5.3.0 (2022-12-23) 92 | 93 | #### Added 94 | 95 | - **cli**: Added a percentage display in watch mode 96 | - Added a `flake.nix` for Nix users 97 | 98 | #### Changed 99 | 100 | - **structs3**: Added an additional test 101 | - **macros**: Added a link to MacroKata in the README 102 | 103 | #### Fixed 104 | 105 | - **strings3**: Added a link to `std` in the hint 106 | - **threads1**: Corrected a hint link 107 | - **iterators1**: Clarified hint steps 108 | - **errors5**: Fix a typo in the hint 109 | - **options1**: Clarified on the usage of the 24-hour system 110 | - **threads2, threads3**: Explicitly use `Arc::clone` 111 | - **structs3**: Clarifed the hint 112 | - **quiz2, as_ref_mut, options1, traits1, traits2**: Clarified hints 113 | - **traits1, traits2, cli**: Tidied up unmatching backticks 114 | - **enums2**: Removed unneccessary indirection of self 115 | - **enums3**: Added an extra tuple comment 116 | 117 | #### Housekeeping 118 | 119 | - Added a VSCode extension recommendation 120 | - Applied some Clippy and rustfmt formatting 121 | - Added a note on Windows PowerShell and other shell compatibility 122 | 123 | 124 | 125 | ## 5.2.1 (2022-09-06) 126 | 127 | #### Fixed 128 | 129 | - **quiz1**: Reworded the comment to actually reflect what's going on in the tests. 130 | Also added another assert just to make sure. 131 | - **rc1**: Fixed a typo in the hint. 132 | - **lifetimes**: Add quotes to the `println!` output, for readability. 133 | 134 | #### Housekeeping 135 | 136 | - Fixed a typo in README.md 137 | 138 | 139 | 140 | ## 5.2.0 (2022-08-27) 141 | 142 | #### Added 143 | 144 | - Added a `reset` command 145 | 146 | #### Changed 147 | 148 | - **options2**: Convert the exercise to use tests 149 | 150 | #### Fixed 151 | 152 | - **threads3**: Fixed a typo 153 | - **quiz1**: Adjusted the explanations to be consistent with 154 | the tests 155 | 156 | 157 | 158 | ## 5.1.1 (2022-08-17) 159 | 160 | #### Bug Fixes 161 | 162 | - Fixed an incorrect assertion in options1 163 | 164 | 165 | 166 | ## 5.1.0 (2022-08-16) 167 | 168 | #### Features 169 | 170 | - Added a new `rc1` exercise. 171 | - Added a new `cow1` exercise. 172 | 173 | #### Bug Fixes 174 | 175 | - **variables5**: Corrected reference to previous exercise 176 | - **functions4**: Fixed line number reference 177 | - **strings3**: Clarified comment wording 178 | - **traits4, traits5**: Fixed line number reference 179 | - **traits5**: 180 | - Fixed typo in "parameter" 181 | - Made exercise prefer a traits-based solution 182 | - **lifetimes2**: Improved hint 183 | - **threads3**: Fixed typo in hint 184 | - **box1**: Replaced `unimplemented!` with `todo!` 185 | - **errors5**: Provided an explanation for usage of `Box` 186 | - **quiz2**: Fixed a typo 187 | - **macros**: Updated the macros book link 188 | - **options1**: 189 | - Removed unused code 190 | - Added more granular tests 191 | - Fixed some comment syntax shenanigans in info.toml 192 | 193 | #### Housekeeping 194 | 195 | - Fixed a typo in .editorconfig 196 | - Fixed a typo in integration_tests.rs 197 | - Clarified manual installation instructions using `cargo install --path .` 198 | - Added a link to our Zulip in the readme file 199 | 200 | 201 | 202 | ## 5.0.0 (2022-07-16) 203 | 204 | #### Features 205 | 206 | - Hint comments in exercises now also include a reference to the 207 | `hint` watch mode subcommand. 208 | - **intro1**: Added more hints to point the user to the source file. 209 | - **variables**: Switched variables3 and variables4. 210 | - Moved `vec` and `primitive_types` exercises before `move_semantics`. 211 | - Renamed `vec` to `vecs` to be more in line with the naming in general. 212 | - Split up the `collections` exercises in their own folders. 213 | - **vec2**: Added a second part of the function that provides an alternative, 214 | immutable way of modifying vec values. 215 | - **enums3**: Added a hint. 216 | - Moved `strings` before `modules`. 217 | - Added a `strings3` exercise to teach modifying strings. 218 | - Added a `hashmaps3` exercise for some advanced usage of hashmaps. 219 | - Moved the original `quiz2` to be `strings4`, since it only tested strings 220 | anyways. 221 | - Reworked `quiz2` into a new exercise that tests more chapters. 222 | - Renamed `option` to `options`. 223 | - **options1**: Rewrote parts of the exercise to remove the weird array 224 | iteration stuff. 225 | - Moved `generics3` to be `quiz3`. 226 | - Moved box/arc exercises behind `iterators`. 227 | - **iterators4**: Added a test for factorials of zero. 228 | - Split `threads1` between two exercises, the first one focusing more on 229 | `JoinHandle`s. 230 | - Added a `threads3` exercises that uses `std::sync::mpsc`. 231 | - Added a `clippy3` exercises with some more interesting checks. 232 | - **as_ref_mut**: Added a section that actually tests `AsMut`. 233 | - Added 3 new lifetimes exercises. 234 | - Added 3 new traits exercises. 235 | 236 | #### Bug Fixes 237 | 238 | - **variables2**: Made output messages more verbose. 239 | - **variables5**: Added a nudging hint about shadowing. 240 | - **variables6**: Fixed link to book. 241 | - **functions**: Clarified the README wording. Generally cleaned up 242 | some hints and added some extra comments. 243 | - **if2**: Renamed function name to `foo_if_fizz`. 244 | - **move_semantics**: Clarified some hints. 245 | - **quiz1**: Renamed the function name to be more verbose. 246 | - **structs1**: Use an integer type instead of strings. Renamed "unit structs" 247 | to "unit-like structs", as is used in the book. 248 | - **structs3**: Added the `panic!` statement in from the beginning. 249 | - **errors1**: Use `is_empty()` instead of `len() > 0` 250 | - **errors3**: Improved the hint. 251 | - **errors5**: Improved exercise instructions and the hint. 252 | - **errors6**: Provided the skeleton of one of the functions that's supposed 253 | to be implemented. 254 | - **iterators3**: Inserted `todo!` into `divide()` to keep a compiler error 255 | from happening. 256 | - **from_str**: Added a hint comment about string error message conversion with 257 | `Box`. 258 | - **try_from_into**: Fixed the function name in comment. 259 | 260 | #### Removed 261 | 262 | - Removed the legacy LSP feature that was using `mod.rs` files. 263 | - Removed `quiz4`. 264 | - Removed `advanced_errs`. These were the last exercises in the recommended 265 | order, and I've always felt like they didn't quite fit in with the mostly 266 | simple, book-following style we've had in Rustlings. 267 | 268 | #### Housekeeping 269 | 270 | - Added missing exercises to the book index. 271 | - Updated spacing in Cargo.toml. 272 | - Added a GitHub actions config so that tests run on every PR/commit. 273 | 274 | 275 | 276 | ## 4.8.0 (2022-07-01) 277 | 278 | #### Features 279 | 280 | - Added a progress indicator for `rustlings watch`. 281 | - The installation script now checks for Rustup being installed. 282 | - Added a `rustlings lsp` command to enable `rust-analyzer`. 283 | 284 | #### Bug Fixes 285 | 286 | - **move_semantics5**: Replaced "in vogue" with "in scope" in hint. 287 | - **if2**: Fixed a typo in the hint. 288 | - **variables1**: Fixed an incorrect line reference in the hint. 289 | - Fixed an out of bounds check in the installation Bash script. 290 | 291 | #### Housekeeping 292 | 293 | - Replaced the git.io URL with the fully qualified URL because of git.io's sunsetting. 294 | - Removed the deprecated Rust GitPod extension. 295 | 296 | 297 | 298 | ## 4.7.1 (2022-04-20) 299 | 300 | #### Features 301 | 302 | - The amount of dependency crates that need to be compiled went down from ~65 to 303 | ~45 by bumping dependency versions. 304 | - The minimum Rust version in the install scripts has been bumped to 1.56.0 (this isn't in 305 | the release itself, since install scripts don't really get versioned) 306 | 307 | #### Bug Fixes 308 | 309 | - **arc1**: A small part has been rewritten using a more functional code style (#968). 310 | - **using_as**: A small part has been refactored to use `sum` instead of `fold`, resulting 311 | in better readability. 312 | 313 | #### Housekeeping 314 | 315 | - The changelog will now be manually written instead of being automatically generated by the 316 | Git log. 317 | 318 | 319 | 320 | ## 4.7.0 (2022-04-14) 321 | 322 | #### Features 323 | 324 | - Add move_semantics6.rs exercise (#908) ([3f0e1303](https://github.com/rust-lang/rustlings/commit/3f0e1303e0b3bf3fecc0baced3c8b8a37f83c184)) 325 | - **intro:** Add intro section. ([21c9f441](https://github.com/rust-lang/rustlings/commit/21c9f44168394e08338fd470b5f49b1fd235986f)) 326 | - Include exercises folder in the project structure behind a feature, enabling rust-analyzer to work (#917) ([179a75a6](https://github.com/rust-lang/rustlings/commit/179a75a68d03ac9518dec2297fb17f91a4fc506b)) 327 | 328 | #### Bug Fixes 329 | 330 | - Fix a few spelling mistakes ([1c0fe3cb](https://github.com/rust-lang/rustlings/commit/1c0fe3cbcca85f90b3985985b8e265ee872a2ab2)) 331 | - **cli:** 332 | - Move long text strings into constants. ([f78c4802](https://github.com/rust-lang/rustlings/commit/f78c48020830d7900dd8d81f355606581670446d)) 333 | - Replace `filter_map()` with `find_map()` ([9b27e8d](https://github.com/rust-lang/rustlings/commit/9b27e8d993ca20232fe38a412750c3f845a83b65)) 334 | - **clippy1:** 335 | - Set clippy::float_cmp lint to deny (#907) ([71a06044](https://github.com/rust-lang/rustlings/commit/71a06044e6a96ff756dc31d7b0ed665ae4badb57)) 336 | - Updated code to test correctness clippy lint with approx_constant lint rule ([f2650de3](https://github.com/rust-lang/rustlings/commit/f2650de369810867d2763e935ac0963c32ec420e)) 337 | - **errors1:** 338 | - Add a comment to make the purpose more clear (#486) ([cbcde345](https://github.com/rust-lang/rustlings/commit/cbcde345409c3e550112e449242848eaa3391bb6)) 339 | - Don't modify tests (#958) ([60bb7cc](https://github.com/rust-lang/rustlings/commit/60bb7cc3931d21d3986ad52b2b302e632a93831c)) 340 | - **errors6:** Remove existing answer code ([43d0623](https://github.com/rust-lang/rustlings/commit/43d0623086edbc46fe896ba59c7afa22c3da9f7a)) 341 | - **functions5:** Remove wrong new line and small English improvements (#885) ([8ef4869b](https://github.com/rust-lang/rustlings/commit/8ef4869b264094e5a9b50452b4534823a9df19c3)) 342 | - **install:** protect path with whitespaces using quotes and stop at the first error ([d114847f](https://github.com/rust-lang/rustlings/commit/d114847f256c5f571c0b4c87e04b04bce3435509)) 343 | - **intro1:** Add compiler error explanation. ([9b8de655](https://github.com/rust-lang/rustlings/commit/9b8de65525a5576b78cf0c8e4098cdd34296338f)) 344 | - **iterators1:** reorder TODO steps ([0bd7a063](https://github.com/rust-lang/rustlings/commit/0bd7a0631a17a9d69af5746795a30efc9cf64e6e)) 345 | - **move_semantics2:** Add comment ([89650f80](https://github.com/rust-lang/rustlings/commit/89650f808af23a32c9a2c6d46592b77547a6a464)) 346 | - **move_semantics5:** correct typo (#857) ([46c28d5c](https://github.com/rust-lang/rustlings/commit/46c28d5cef3d8446b5a356b19d8dbc725f91a3a0)) 347 | - **quiz1:** update to say quiz covers "If" ([1622e8c1](https://github.com/rust-lang/rustlings/commit/1622e8c198d89739765c915203efff0091bdeb78)) 348 | - **structs3:** 349 | - Add a hint for panic (#608) ([4f7ff5d9](https://github.com/rust-lang/rustlings/commit/4f7ff5d9c7b2d8b045194c1a9469d37e30257c4a)) 350 | - remove redundant 'return' (#852) ([bf33829d](https://github.com/rust-lang/rustlings/commit/bf33829da240375d086f96267fc2e02fa6b07001)) 351 | - Assigned value to `cents_per_gram` in test ([d1ee2da](https://github.com/rust-lang/rustlings/commit/d1ee2daf14f19105e6db3f9c610f44293d688532)) 352 | - **structs3.rs:** assigned value to cents_per_gram in test ([d1ee2daf](https://github.com/rust-lang/rustlings/commit/d1ee2daf14f19105e6db3f9c610f44293d688532)) 353 | - **traits1:** rename test functions to snake case (#854) ([1663a16e](https://github.com/rust-lang/rustlings/commit/1663a16eade6ca646b6ed061735f7982434d530d)) 354 | 355 | #### Documentation improvements 356 | 357 | - Add hints on how to get GCC installed (#741) ([bc56861](https://github.com/rust-lang/rustlings/commit/bc5686174463ad6f4f6b824b0e9b97c3039d4886)) 358 | - Fix some code blocks that were not highlighted ([17f9d74](https://github.com/rust-lang/rustlings/commit/17f9d7429ccd133a72e815fb5618e0ce79560929)) 359 | 360 | 361 | 362 | ## 4.6.0 (2021-09-25) 363 | 364 | #### Features 365 | 366 | - add advanced_errs2 ([abd6b70c](https://github.com/rust-lang/rustlings/commit/abd6b70c72dc6426752ff41f09160b839e5c449e)) 367 | - add advanced_errs1 ([882d535b](https://github.com/rust-lang/rustlings/commit/882d535ba8628d5e0b37e8664b3e2f26260b2671)) 368 | - Add a farewell message when quitting `watch` ([1caef0b4](https://github.com/rust-lang/rustlings/commit/1caef0b43494c8b8cdd6c9260147e70d510f1aca)) 369 | - add more watch commands ([a7dc080b](https://github.com/rust-lang/rustlings/commit/a7dc080b95e49146fbaafe6922a6de2f8cb1582a), closes [#842](https://github.com/rust-lang/rustlings/issues/842)) 370 | - **modules:** update exercises, add modules3 (#822) ([dfd2fab4](https://github.com/rust-lang/rustlings/commit/dfd2fab4f33d1bf59e2e5ee03123c0c9a67a9481)) 371 | - **quiz1:** add default function name in comment (#838) ([0a11bad7](https://github.com/rust-lang/rustlings/commit/0a11bad71402b5403143d642f439f57931278c07)) 372 | 373 | #### Bug Fixes 374 | 375 | - Correct small typo in exercises/conversions/from_str.rs ([86cc8529](https://github.com/rust-lang/rustlings/commit/86cc85295ae36948963ae52882e285d7e3e29323)) 376 | - **cli:** typo in exercise.rs (#848) ([06d5c097](https://github.com/rust-lang/rustlings/commit/06d5c0973a3dffa3c6c6f70acb775d4c6630323c)) 377 | - **from_str, try_from_into:** custom error types ([2dc93cad](https://github.com/rust-lang/rustlings/commit/2dc93caddad43821743e4903d89b355df58d7a49)) 378 | - **modules2:** fix typo (#835) ([1c3beb0a](https://github.com/rust-lang/rustlings/commit/1c3beb0a59178c950dc05fe8ee2346b017429ae0)) 379 | - **move_semantics5:** 380 | - change &mut \*y to &mut x (#814) ([d75759e8](https://github.com/rust-lang/rustlings/commit/d75759e829fdcd64ef071cf4b6eae2a011a7718b)) 381 | - Clarify instructions ([df25684c](https://github.com/rust-lang/rustlings/commit/df25684cb79f8413915e00b5efef29369849cef1)) 382 | - **quiz1:** Fix inconsistent wording (#826) ([03131a3d](https://github.com/rust-lang/rustlings/commit/03131a3d35d9842598150f9da817f7cc26e2669a)) 383 | 384 | 385 | 386 | ## 4.5.0 (2021-07-07) 387 | 388 | #### Features 389 | 390 | - Add move_semantics5 exercise. (#746) ([399ab328](https://github.com/rust-lang/rustlings/commit/399ab328d8d407265c09563aa4ef4534b2503ff2)) 391 | - **cli:** Add "next" to run the next unsolved exercise. (#785) ([d20e413a](https://github.com/rust-lang/rustlings/commit/d20e413a68772cd493561f2651cf244e822b7ca5)) 392 | 393 | #### Bug Fixes 394 | 395 | - rename result1 to errors4 ([50ab289d](https://github.com/rust-lang/rustlings/commit/50ab289da6b9eb19a7486c341b00048c516b88c0)) 396 | - move_semantics5 hints ([1b858285](https://github.com/rust-lang/rustlings/commit/1b85828548f46f58b622b5e0c00f8c989f928807)) 397 | - remove trailing whitespaces from iterators1 ([4d4fa774](https://github.com/rust-lang/rustlings/commit/4d4fa77459392acd3581c6068aa8be9a02de12fc)) 398 | - add hints to generics1 and generics2 exercises ([31457940](https://github.com/rust-lang/rustlings/commit/31457940846b3844d78d4a4d2b074bc8d6aaf1eb)) 399 | - remove trailing whitespace ([d9b69bd1](https://github.com/rust-lang/rustlings/commit/d9b69bd1a0a7a99f2c0d80933ad2eea44c8c71b2)) 400 | - **installation:** first PowerShell command ([aa9a943d](https://github.com/rust-lang/rustlings/commit/aa9a943ddf3ae260782e73c26bcc9db60e5894b6)) 401 | - **iterators5:** derive Clone, Copy ([91fc9e31](https://github.com/rust-lang/rustlings/commit/91fc9e3118f4af603c9911698cc2a234725cb032)) 402 | - **quiz1:** Updated question description (#794) ([d8766496](https://github.com/rust-lang/rustlings/commit/d876649616cc8a8dd5f539f8bc1a5434b960b1e9)) 403 | - **try_from_into, from_str:** hints for dyn Error ([11d2cf0d](https://github.com/rust-lang/rustlings/commit/11d2cf0d604dee3f5023c17802d69438e69fa50e)) 404 | - **variables5:** confine the answer further ([48ffcbd2](https://github.com/rust-lang/rustlings/commit/48ffcbd2c4cc4d936c2c7480019190f179813cc5)) 405 | 406 | 407 | 408 | ## 4.4.0 (2021-04-24) 409 | 410 | #### Bug Fixes 411 | 412 | - Fix spelling error in main.rs ([91ee27f2](https://github.com/rust-lang/rustlings/commit/91ee27f22bd3797a9db57e5fd430801c170c5db8)) 413 | - typo in default out text ([644c49f1](https://github.com/rust-lang/rustlings/commit/644c49f1e04cbb24e95872b3a52b07d692ae3bc8)) 414 | - **collections:** Naming exercises for vectors and hashmap ([bef39b12](https://github.com/rust-lang/rustlings/commit/bef39b125961310b34b34871e480a82e82af4678)) 415 | - **from_str:** 416 | - Correct typos ([5f7c89f8](https://github.com/rust-lang/rustlings/commit/5f7c89f85db1f33da01911eaa479c3a2d4721678)) 417 | - test for error instead of unwrap/should_panic ([15e71535](https://github.com/rust-lang/rustlings/commit/15e71535f37cfaed36e22eb778728d186e2104ab)) 418 | - use trait objects for from_str ([c3e7b831](https://github.com/rust-lang/rustlings/commit/c3e7b831786c9172ed8bd5d150f3c432f242fba9)) 419 | - **functions3:** improve function argument type (#687) ([a6509cc4](https://github.com/rust-lang/rustlings/commit/a6509cc4d545d8825f01ddf7ee37823b372154dd)) 420 | - **hashmap2:** Update incorrect assertion (#660) ([72aaa15e](https://github.com/rust-lang/rustlings/commit/72aaa15e6ab4b72b3422f1c6356396e20a2a2bb8)) 421 | - **info:** Fix typo (#635) ([cddc1e86](https://github.com/rust-lang/rustlings/commit/cddc1e86e7ec744ee644cc774a4887b1a0ded3e8)) 422 | - **iterators2:** Moved errors out of tests. ([baf4ba17](https://github.com/rust-lang/rustlings/commit/baf4ba175ba6eb92989e3dd54ecbec4bedc9a863), closes [#359](https://github.com/rust-lang/rustlings/issues/359)) 423 | - **iterators3:** Enabled iterators3.rs to run without commented out tests. ([c6712dfc](https://github.com/rust-lang/rustlings/commit/c6712dfccd1a093e590ad22bbc4f49edc417dac0)) 424 | - **main:** Let find_exercise work with borrows ([347f30bd](https://github.com/rust-lang/rustlings/commit/347f30bd867343c5ace1097e085a1f7e356553f7)) 425 | - **move_semantics4:** 426 | - Remove redundant "instead" (#640) ([cc266d7d](https://github.com/rust-lang/rustlings/commit/cc266d7d80b91e79df3f61984f231b7f1587218e)) 427 | - Small readbility improvement (#617) ([10965920](https://github.com/rust-lang/rustlings/commit/10965920fbdf8a1efc85bed869e55a1787006404)) 428 | - **option2:** Rename uninformative variables (#675) ([b4de6594](https://github.com/rust-lang/rustlings/commit/b4de6594380636817d13c2677ec6f472a964cf43)) 429 | - **quiz3:** Force an answer to Q2 (#672) ([0d894e6f](https://github.com/rust-lang/rustlings/commit/0d894e6ff739943901e1ae8c904582e5c2f843bd)) 430 | - **structs:** Add 5.3 to structs/README (#652) ([6bd791f2](https://github.com/rust-lang/rustlings/commit/6bd791f2f44aa7f0ad926df767f6b1fa8f12a9a9)) 431 | - **structs2:** correct grammar in hint (#663) ([ebdb66c7](https://github.com/rust-lang/rustlings/commit/ebdb66c7bfb6d687a14cc511a559a222e6fc5de4)) 432 | - **structs3:** 433 | - reword heading comment (#664) ([9f3e8c2d](https://github.com/rust-lang/rustlings/commit/9f3e8c2dde645e5264c2d2200e68842b5f47bfa3)) 434 | - add check to prevent naive implementation of is_international ([05a753fe](https://github.com/rust-lang/rustlings/commit/05a753fe6333d36dbee5f68c21dec04eacdc75df)) 435 | - **threads1:** line number correction ([7857b0a6](https://github.com/rust-lang/rustlings/commit/7857b0a689b0847f48d8c14cbd1865e3b812d5ca)) 436 | - **try_from_into:** use trait objects ([2e93a588](https://github.com/rust-lang/rustlings/commit/2e93a588e0abe8badb7eafafb9e7d073c2be5df8)) 437 | 438 | #### Features 439 | 440 | - Replace clap with argh ([7928122f](https://github.com/rust-lang/rustlings/commit/7928122fcef9ca7834d988b1ec8ca0687478beeb)) 441 | - Replace emojis when NO_EMOJI env variable present ([8d62a996](https://github.com/rust-lang/rustlings/commit/8d62a9963708dbecd9312e8bcc4b47049c72d155)) 442 | - Added iterators5.rs exercise. ([b29ea17e](https://github.com/rust-lang/rustlings/commit/b29ea17ea94d1862114af2cf5ced0e09c197dc35)) 443 | - **arc1:** Add more details to description and hint (#710) ([81be4044](https://github.com/rust-lang/rustlings/commit/81be40448777fa338ebced3b0bfc1b32d6370313)) 444 | - **cli:** Improve the list command with options, and then some ([8bbe4ff1](https://github.com/rust-lang/rustlings/commit/8bbe4ff1385c5c169c90cd3ff9253f9a91daaf8e)) 445 | - **list:** 446 | - updated progress percentage ([1c6f7e4b](https://github.com/rust-lang/rustlings/commit/1c6f7e4b7b9b3bd36f4da2bb2b69c549cc8bd913)) 447 | - added progress info ([c0e3daac](https://github.com/rust-lang/rustlings/commit/c0e3daacaf6850811df5bc57fa43e0f249d5cfa4)) 448 | 449 | 450 | 451 | ## 4.3.0 (2020-12-29) 452 | 453 | #### Features 454 | 455 | - Rewrite default out text ([44d39112](https://github.com/rust-lang/rustlings/commit/44d39112ff122b29c9793fe52e605df1612c6490)) 456 | - match exercise order to book chapters (#541) ([033bf119](https://github.com/rust-lang/rustlings/commit/033bf1198fc8bfce1b570e49da7cde010aa552e3)) 457 | - Crab? (#586) ([fa9f522b](https://github.com/rust-lang/rustlings/commit/fa9f522b7f043d7ef73a39f003a9272dfe72c4f4)) 458 | - add "rustlings list" command ([838f9f30](https://github.com/rust-lang/rustlings/commit/838f9f30083d0b23fd67503dcf0fbeca498e6647)) 459 | - **try_from_into:** remove duplicate annotation ([04f1d079](https://github.com/rust-lang/rustlings/commit/04f1d079aa42a2f49af694bc92c67d731d31a53f)) 460 | 461 | #### Bug Fixes 462 | 463 | - update structs README ([bcf14cf6](https://github.com/rust-lang/rustlings/commit/bcf14cf677adb3a38a3ac3ca53f3c69f61153025)) 464 | - added missing exercises to info.toml ([90cfb6ff](https://github.com/rust-lang/rustlings/commit/90cfb6ff28377531bfc34acb70547bdb13374f6b)) 465 | - gives a bit more context to magic number ([30644c9a](https://github.com/rust-lang/rustlings/commit/30644c9a062b825c0ea89435dc59f0cad86b110e)) 466 | - **functions2:** Change signature to trigger precise error message: (#605) ([0ef95947](https://github.com/rust-lang/rustlings/commit/0ef95947cc30482e63a7045be6cc2fb6f6dcb4cc)) 467 | - **structs1:** Adjust wording (#573) ([9334783d](https://github.com/rust-lang/rustlings/commit/9334783da31d821cc59174fbe8320df95828926c)) 468 | - **try_from_into:** 469 | - type error ([4f4cfcf3](https://github.com/rust-lang/rustlings/commit/4f4cfcf3c36c8718c7c170c9c3a6935e6ef0618c)) 470 | - Update description (#584) ([96347df9](https://github.com/rust-lang/rustlings/commit/96347df9df294f01153b29d9ad4ba361f665c755)) 471 | - **vec1:** Have test compare every element in a and v ([9b6c6293](https://github.com/rust-lang/rustlings/commit/9b6c629397b24b944f484f5b2bbd8144266b5695)) 472 | 473 | 474 | 475 | ## 4.2.0 (2020-11-07) 476 | 477 | #### Features 478 | 479 | - Add HashMap exercises ([633c00cf](https://github.com/rust-lang/rustlings/commit/633c00cf8071e1e82959a3010452a32f34f29fc9)) 480 | - Add Vec exercises ([0c12fa31](https://github.com/rust-lang/rustlings/commit/0c12fa31c57c03c6287458a0a8aca7afd057baf6)) 481 | - **primitive_types6:** Add a test (#548) ([2b1fb2b7](https://github.com/rust-lang/rustlings/commit/2b1fb2b739bf9ad8d6b7b12af25fee173011bfc4)) 482 | - **try_from_into:** Add tests (#571) ([95ccd926](https://github.com/rust-lang/rustlings/commit/95ccd92616ae79ba287cce221101e0bbe4f68cdc)) 483 | 484 | #### Bug Fixes 485 | 486 | - log error output when inotify limit is exceeded ([d61b4e5a](https://github.com/rust-lang/rustlings/commit/d61b4e5a13b44d72d004082f523fa1b6b24c1aca)) 487 | - more unique temp_file ([5643ef05](https://github.com/rust-lang/rustlings/commit/5643ef05bc81e4a840e9456f4406a769abbe1392)) 488 | - **installation:** Update the MinRustVersion ([21bfb2d4](https://github.com/rust-lang/rustlings/commit/21bfb2d4777429c87d8d3b5fbf0ce66006dcd034)) 489 | - **iterators2:** Update description (#578) ([197d3a3d](https://github.com/rust-lang/rustlings/commit/197d3a3d8961b2465579218a6749b2b2cefa8ddd)) 490 | - **primitive_types6:** 491 | - remove 'unused doc comment' warning ([472d8592](https://github.com/rust-lang/rustlings/commit/472d8592d65c8275332a20dfc269e7ac0d41bc88)) 492 | - missing comma in test ([4fb230da](https://github.com/rust-lang/rustlings/commit/4fb230daf1251444fcf29e085cee222a91f8a37e)) 493 | - **quiz3:** Second test is for odd numbers, not even. (#553) ([18e0bfef](https://github.com/rust-lang/rustlings/commit/18e0bfef1de53071e353ba1ec5837002ff7290e6)) 494 | 495 | 496 | 497 | ## 4.1.0 (2020-10-05) 498 | 499 | #### Bug Fixes 500 | 501 | - Update rustlings version in Cargo.lock ([1cc40bc9](https://github.com/rust-lang/rustlings/commit/1cc40bc9ce95c23d56f6d91fa1c4deb646231fef)) 502 | - **arc1:** index mod should equal thread count ([b4062ef6](https://github.com/rust-lang/rustlings/commit/b4062ef6993e80dac107c4093ea85166ad3ee0fa)) 503 | - **enums3:** Update Message::ChangeColor to take a tuple. (#457) ([4b6540c7](https://github.com/rust-lang/rustlings/commit/4b6540c71adabad647de8a09e57295e7c7c7d794)) 504 | - **exercises:** adding question mark to quiz2 ([101072ab](https://github.com/rust-lang/rustlings/commit/101072ab9f8c80b40b8b88cb06cbe38aca2481c5)) 505 | - **generics3:** clarify grade change ([47f7672c](https://github.com/rust-lang/rustlings/commit/47f7672c0307732056e7426e81d351f0dd7e22e5)) 506 | - **structs3:** Small adjustment of variable name ([114b54cb](https://github.com/rust-lang/rustlings/commit/114b54cbdb977234b39e5f180d937c14c78bb8b2)) 507 | - **using_as:** Add test so that proper type is returned. (#512) ([3286c5ec](https://github.com/rust-lang/rustlings/commit/3286c5ec19ea5fb7ded81d047da5f8594108a490)) 508 | 509 | #### Features 510 | 511 | - Added iterators1.rs exercise ([9642f5a3](https://github.com/rust-lang/rustlings/commit/9642f5a3f686270a4f8f6ba969919ddbbc4f7fdd)) 512 | - Add ability to run rustlings on repl.it (#471) ([8f7b5bd0](https://github.com/rust-lang/rustlings/commit/8f7b5bd00eb83542b959830ef55192d2d76db90a)) 513 | - Add gitpod support (#473) ([4821a8be](https://github.com/rust-lang/rustlings/commit/4821a8be94af4f669042a06ab917934cfacd032f)) 514 | - Remind the user of the hint option (#425) ([816b1f5e](https://github.com/rust-lang/rustlings/commit/816b1f5e85d6cc6e72673813a85d0ada2a8f84af)) 515 | - Remind the user of the hint option (#425) ([9f61db5d](https://github.com/rust-lang/rustlings/commit/9f61db5dbe38538cf06571fcdd5f806e7901e83a)) 516 | - **cli:** Added 'cls' command to 'watch' mode (#474) ([4f2468e1](https://github.com/rust-lang/rustlings/commit/4f2468e14f574a93a2e9b688367b5752ed96ae7b)) 517 | - **try_from_into:** Add insufficient length test (#469) ([523d18b8](https://github.com/rust-lang/rustlings/commit/523d18b873a319f7c09262f44bd40e2fab1830e5)) 518 | 519 | 520 | 521 | ## 4.0.0 (2020-07-08) 522 | 523 | #### Breaking Changes 524 | 525 | - Add a --nocapture option to display test harnesses' outputs ([8ad5f9bf](https://github.com/rust-lang/rustlings/commit/8ad5f9bf531a4848b1104b7b389a20171624c82f)) 526 | - Rename test to quiz, fixes #244 ([010a0456](https://github.com/rust-lang/rustlings/commit/010a04569282149cea7f7a76fc4d7f4c9f0f08dd)) 527 | 528 | #### Features 529 | 530 | - Add traits README ([173bb141](https://github.com/rust-lang/rustlings/commit/173bb14140c5530cbdb59e53ace3991a99d804af)) 531 | - Add box1.rs exercise ([7479a473](https://github.com/rust-lang/rustlings/commit/7479a4737bdcac347322ad0883ca528c8675e720)) 532 | - Rewrite try_from_into (#393) ([763aa6e3](https://github.com/rust-lang/rustlings/commit/763aa6e378a586caae2d8d63755a85eeba227933)) 533 | - Add if2 exercise ([1da84b5f](https://github.com/rust-lang/rustlings/commit/1da84b5f7c489f65bd683c244f13c7d1ee812df0)) 534 | - Added exercise structs3.rs ([b66e2e09](https://github.com/rust-lang/rustlings/commit/b66e2e09622243e086a0f1258dd27e1a2d61c891)) 535 | - Add exercise variables6 covering const (#352) ([5999acd2](https://github.com/rust-lang/rustlings/commit/5999acd24a4f203292be36e0fd18d385887ec481)) 536 | 537 | #### Bug Fixes 538 | 539 | - Change then to than ([ddd98ad7](https://github.com/rust-lang/rustlings/commit/ddd98ad75d3668fbb10eff74374148aa5ed2344d)) 540 | - rename quiz1 to tests1 in info (#420) ([0dd1c6ca](https://github.com/rust-lang/rustlings/commit/0dd1c6ca6b389789e0972aa955fe17aa15c95f29)) 541 | - fix quiz naming inconsistency (#421) ([5563adbb](https://github.com/rust-lang/rustlings/commit/5563adbb890587fc48fbbc9c4028642687f1e85b)) 542 | - confine the user further in variable exercises ([06ef4cc6](https://github.com/rust-lang/rustlings/commit/06ef4cc654e75d22a526812919ee49b8956280bf)) 543 | - update iterator and macro text for typos and clarity ([95900828](https://github.com/rust-lang/rustlings/commit/959008284834bece0196a01e17ac69a7e3590116)) 544 | - update generics2 closes #362 ([964c974a](https://github.com/rust-lang/rustlings/commit/964c974a0274199d755073b917c2bc5da0c9b4f1)) 545 | - confusing comment in conversions/try_from_into.rs ([c9e4f2cf](https://github.com/rust-lang/rustlings/commit/c9e4f2cfb4c48d0b7451263cfb43b9426438122d)) 546 | - **arc1:** Passively introduce attributes (#429) ([113cdae2](https://github.com/rust-lang/rustlings/commit/113cdae2d4e4c55905e8056ad326ede7fd7de356)) 547 | - **box1:** fix comment typo (#426) ([bb2ca251](https://github.com/rust-lang/rustlings/commit/bb2ca251106b27a7272d9a30872904dd1376654c)) 548 | - **errorsn:** Try harder to confine the user. (#388) ([2b20c8a0](https://github.com/rust-lang/rustlings/commit/2b20c8a0f5774d07c58d110d75879f33fc6273b5)) 549 | - **from_into.rs:** typo ([a901499e](https://github.com/rust-lang/rustlings/commit/a901499ededd3ce1995164700514fe4e9a0373ea)) 550 | - **generics2:** Guide students to the answer (#430) ([e6bd8021](https://github.com/rust-lang/rustlings/commit/e6bd8021d9a7dd06feebc30c9d5f953901d7b419)) 551 | - **installation:** 552 | - Provide a backup git reference when tag can't be curl ([9e4fb100](https://github.com/rust-lang/rustlings/commit/9e4fb1009f1c9e3433915c03e22c2af422e5c5fe)) 553 | - Check if python is available while checking for git,rustc and cargo ([9cfb617d](https://github.com/rust-lang/rustlings/commit/9cfb617d5b0451b4b51644a1298965390cda9884)) 554 | - **option1:** 555 | - Don't add only zeros to the numbers array ([cce6a442](https://github.com/rust-lang/rustlings/commit/cce6a4427718724a9096800754cd3abeca6a1580)) 556 | - Add cast to usize, as it is confusing in the context of an exercise about Option ([f6cffc7e](https://github.com/rust-lang/rustlings/commit/f6cffc7e487b42f15a6f958e49704c93a8d4465b)) 557 | - **option2:** Add TODO to comments (#400) ([10967bce](https://github.com/rust-lang/rustlings/commit/10967bce57682812dc0891a9f9757da1a9d87404)) 558 | - **options1:** Add hint about Array Initialization (#389) ([9f75554f](https://github.com/rust-lang/rustlings/commit/9f75554f2a30295996f03f0160b98c0458305502)) 559 | - **test2:** name of type String and &str (#394) ([d6c0a688](https://github.com/rust-lang/rustlings/commit/d6c0a688e6a96f93ad60d540d4b326f342fc0d45)) 560 | - **variables6:** minor typo (#419) ([524e17df](https://github.com/rust-lang/rustlings/commit/524e17df10db95f7b90a0f75cc8997182a8a4094)) 561 | 562 | 563 | 564 | ## 3.0.0 (2020-04-11) 565 | 566 | #### Breaking Changes 567 | 568 | - make "compile" exercises print output (#278) ([3b6d5c](https://github.com/fmoko/rustlings/commit/3b6d5c3aaa27a242a832799eb66e96897d26fde3)) 569 | 570 | #### Bug Fixes 571 | 572 | - **primitive_types:** revert primitive_types4 (#296) ([b3a3351e](https://github.com/rust-lang/rustlings/commit/b3a3351e8e6a0bdee07077d7b0382953821649ae)) 573 | - **run:** compile clippy exercise files (#295) ([3ab084a4](https://github.com/rust-lang/rustlings/commit/3ab084a421c0f140ae83bf1fc3f47b39342e7373)) 574 | - **conversions:** 575 | - add additional test to meet exercise rules (#284) ([bc22ec3](https://github.com/fmoko/rustlings/commit/bc22ec382f843347333ef1301fc1bad773657f38)) 576 | - remove duplicate not done comment (#292) ([dab90f](https://github.com/fmoko/rustlings/commit/dab90f7b91a6000fe874e3d664f244048e5fa342)) 577 | - don't hardcode documentation version for traits (#288) ([30e6af](https://github.com/fmoko/rustlings/commit/30e6af60690c326fb5d3a9b7335f35c69c09137d)) 578 | 579 | #### Features 580 | 581 | - add Option2 exercise (#290) ([86b5c08b](https://github.com/rust-lang/rustlings/commit/86b5c08b9bea1576127a7c5f599f5752072c087d)) 582 | - add exercise for option (#282) ([135e5d47](https://github.com/rust-lang/rustlings/commit/135e5d47a7c395aece6f6022117fb20c82f2d3d4)) 583 | - add new exercises for generics (#280) ([76be5e4e](https://github.com/rust-lang/rustlings/commit/76be5e4e991160f5fd9093f03ee2ba260e8f7229)) 584 | - **ci:** add buildkite config ([b049fa2c](https://github.com/rust-lang/rustlings/commit/b049fa2c84dba0f0c8906ac44e28fd45fba51a71)) 585 | 586 | 587 | 588 | ### 2.2.1 (2020-02-27) 589 | 590 | #### Bug Fixes 591 | 592 | - Re-add cloning the repo to install scripts ([3d9b03c5](https://github.com/rust-lang/rustlings/commit/3d9b03c52b8dc51b140757f6fd25ad87b5782ef5)) 593 | 594 | #### Features 595 | 596 | - Add clippy lints (#269) ([1e2fd9c9](https://github.com/rust-lang/rustlings/commit/1e2fd9c92f8cd6e389525ca1a999fca4c90b5921)) 597 | 598 | 599 | 600 | ## 2.2.0 (2020-02-25) 601 | 602 | #### Bug Fixes 603 | 604 | - Update deps to version compatable with aarch64-pc-windows (#263) ([19a93428](https://github.com/rust-lang/rustlings/commit/19a93428b3c73d994292671f829bdc8e5b7b3401)) 605 | - **docs:** 606 | - Added a necessary step to Windows installation process (#242) ([3906efcd](https://github.com/rust-lang/rustlings/commit/3906efcd52a004047b460ed548037093de3f523f)) 607 | - Fixed mangled sentence from book; edited for clarity (#266) ([ade52ff](https://github.com/rust-lang/rustlings/commit/ade52ffb739987287ddd5705944c8777705faed9)) 608 | - Updated iterators readme to account for iterators4 exercise (#273) ([bec8e3a](https://github.com/rust-lang/rustlings/commit/bec8e3a644cbd88db1c73ea5f1d8a364f4a34016)) 609 | - **installation:** make fatal errors more obvious (#272) ([17d0951e](https://github.com/rust-lang/rustlings/commit/17d0951e66fda8e11b204d5c4c41a0d5e22e78f7)) 610 | - **iterators2:** 611 | - Remove reference to missing iterators2.rs (#245) ([419f7797](https://github.com/rust-lang/rustlings/commit/419f7797f294e4ce6a2b883199731b5bde77d262)) 612 | - **as_ref_mut:** Enable a test and improve per clippy's suggestion (#256) ([dfdf809](https://github.com/rust-lang/rustlings/commit/dfdf8093ebbd4145864995627b812780de52f902)) 613 | - **tests1:** 614 | - Change test command ([fe10e06c](https://github.com/rust-lang/rustlings/commit/fe10e06c3733ddb4a21e90d09bf79bfe618e97ce) 615 | - Correct test command in tests1.rs comment (#263) ([39fa7ae](https://github.com/rust-lang/rustlings/commit/39fa7ae8b70ad468da49b06f11b2383135a63bcf)) 616 | 617 | #### Features 618 | 619 | - Add variables5.rs exercise (#264) ([0c73609e](https://github.com/rust-lang/rustlings/commit/0c73609e6f2311295e95d6f96f8c747cfc4cba03)) 620 | - Show a completion message when watching (#253) ([d25ee55a](https://github.com/rust-lang/rustlings/commit/d25ee55a3205882d35782e370af855051b39c58c)) 621 | - Add type conversion and parsing exercises (#249) ([0c85dc11](https://github.com/rust-lang/rustlings/commit/0c85dc1193978b5165491b99cc4922caf8d14a65)) 622 | - Created consistent money unit (#258) ([fd57f8f](https://github.com/rust-lang/rustlings/commit/fd57f8f2c1da2af8ddbebbccec214e6f40f4dbab)) 623 | - Enable test for exercise test4 (#276) ([8b971ff](https://github.com/rust-lang/rustlings/commit/8b971ffab6079a706ac925f5917f987932b55c07)) 624 | - Added traits exercises (#274 but specifically #216, which originally added 625 | this :heart:) ([b559cdd](https://github.com/rust-lang/rustlings/commit/b559cdd73f32c0d0cfc1feda39f82b3e3583df17)) 626 | 627 | 628 | 629 | ## 2.1.0 (2019-11-27) 630 | 631 | #### Bug Fixes 632 | 633 | - add line numbers in several exercises and hints ([b565c4d3](https://github.com/rust-lang/rustlings/commit/b565c4d3e74e8e110bef201a082fa1302722a7c3)) 634 | - **arc1:** Fix some words in the comment ([c42c3b21](https://github.com/rust-lang/rustlings/commit/c42c3b2101df9164c8cd7bb344def921e5ba3e61)) 635 | - **enums:** Add link to chapter on pattern syntax (#242) ([615ce327](https://github.com/rust-lang/rustlings/commit/615ce3279800c56d89f19d218ccb7ef576624feb)) 636 | - **primitive_types4:** 637 | - update outdated hint ([4c5189df](https://github.com/rust-lang/rustlings/commit/4c5189df2bdd9a231f6b2611919ba5aa14da0d3f)) 638 | - update outdated comment ([ded2c034](https://github.com/rust-lang/rustlings/commit/ded2c034ba93fa1e3c2c2ea16b83abc1a57265e8)) 639 | - **strings2:** update line number in hint ([a09f684f](https://github.com/rust-lang/rustlings/commit/a09f684f05c58d239a6fc59ec5f81c2533e8b820)) 640 | - **variables1:** Correct wrong word in comment ([fda5a470](https://github.com/rust-lang/rustlings/commit/fda5a47069e0954f16a04e8e50945e03becb71a5)) 641 | 642 | #### Features 643 | 644 | - **watch:** show hint while watching ([8143d57b](https://github.com/rust-lang/rustlings/commit/8143d57b4e88c51341dd4a18a14c536042cc009c)) 645 | 646 | 647 | 648 | ## 2.0.0 (2019-11-12) 649 | 650 | #### Bug Fixes 651 | 652 | - **default:** Clarify the installation procedure ([c371b853](https://github.com/rust-lang/rustlings/commit/c371b853afa08947ddeebec0edd074b171eeaae0)) 653 | - **info:** Fix trailing newlines for hints ([795b6e34](https://github.com/rust-lang/rustlings/commit/795b6e348094a898e9227a14f6232f7bb94c8d31)) 654 | - **run:** make `run` never prompt ([4b265465](https://github.com/rust-lang/rustlings/commit/4b26546589f7d2b50455429482cf1f386ceae8b3)) 655 | 656 | #### Breaking Changes 657 | 658 | - Refactor hint system ([9bdb0a12](https://github.com/rust-lang/rustlings/commit/9bdb0a12e45a8e9f9f6a4bd4a9c172c5376c7f60)) 659 | - improve `watch` execution mode ([2cdd6129](https://github.com/rust-lang/rustlings/commit/2cdd61294f0d9a53775ee24ad76435bec8a21e60)) 660 | - Index exercises by name ([627cdc07](https://github.com/rust-lang/rustlings/commit/627cdc07d07dfe6a740e885e0ddf6900e7ec336b)) 661 | - **run:** makes `run` never prompt ([4b265465](https://github.com/rust-lang/rustlings/commit/4b26546589f7d2b50455429482cf1f386ceae8b3)) 662 | 663 | #### Features 664 | 665 | - **cli:** check for rustc before doing anything ([36a033b8](https://github.com/rust-lang/rustlings/commit/36a033b87a6549c1e5639c908bf7381c84f4f425)) 666 | - **hint:** Add test for hint ([ce9fa6eb](https://github.com/rust-lang/rustlings/commit/ce9fa6ebbfdc3e7585d488d9409797285708316f)) 667 | 668 | 669 | 670 | ### 1.5.1 (2019-11-11) 671 | 672 | #### Bug Fixes 673 | 674 | - **errors3:** Update hint ([dcfb427b](https://github.com/rust-lang/rustlings/commit/dcfb427b09585f0193f0a294443fdf99f11c64cb), closes [#185](https://github.com/rust-lang/rustlings/issues/185)) 675 | - **if1:** Remove `return` reference ([ad03d180](https://github.com/rust-lang/rustlings/commit/ad03d180c9311c0093e56a3531eec1a9a70cdb45)) 676 | - **strings:** Move Strings before Structs ([6dcecb38](https://github.com/rust-lang/rustlings/commit/6dcecb38a4435593beb87c8e12d6314143631482), closes [#204](https://github.com/rust-lang/rustlings/issues/204)) 677 | - **structs1:** Remove misleading comment ([f72e5a8f](https://github.com/rust-lang/rustlings/commit/f72e5a8f05568dde04eaeac10b9a69872f21cb37)) 678 | - **threads:** Move Threads behind SLT ([fbe91a67](https://github.com/rust-lang/rustlings/commit/fbe91a67a482bfe64cbcdd58d06ba830a0f39da3), closes [#205](https://github.com/rust-lang/rustlings/issues/205)) 679 | - **watch:** clear screen before each `verify()` ([3aff590](https://github.com/rust-lang/rustlings/commit/3aff59085586c24196a547c2693adbdcf4432648)) 680 | 681 | 682 | 683 | ## 1.5.0 (2019-11-09) 684 | 685 | #### Bug Fixes 686 | 687 | - **test1:** Rewrite logic ([79a56942](https://github.com/rust-lang/rustlings/commit/79a569422c8309cfc9e4aed25bf4ab3b3859996b)) 688 | - **installation:** Fix rustlings installation check ([7a252c47](https://github.com/rust-lang/rustlings/commit/7a252c475551486efb52f949b8af55803b700bc6)) 689 | - **iterators:** Rename iterator3.rs ([433d2115](https://github.com/rust-lang/rustlings/commit/433d2115bc1c04b6d34a335a18c9a8f3e2672bc6)) 690 | - **iterators2:** Remove syntax resulting in misleading error message ([4cde8664](https://github.com/rust-lang/rustlings/commit/4cde86643e12db162a66e62f23b78962986046ac)) 691 | - **option1:** 692 | - Fix arguments passed to assert! macro (#222) ([4c2cf6da](https://github.com/rust-lang/rustlings/commit/4c2cf6da755efe02725e05ecc3a303304c10a6da)) 693 | - Fix arguments passed to assert! macro ([ead4f7af](https://github.com/rust-lang/rustlings/commit/ead4f7af9e10e53418efdde5c359159347282afd)) 694 | - Add test for prematurely passing exercise ([a750e4a1](https://github.com/rust-lang/rustlings/commit/a750e4a1a3006227292bb17d57d78ce84da6bfc6)) 695 | - **primitive_types4:** Fail on a slice covering the wrong area ([5b1e673c](https://github.com/rust-lang/rustlings/commit/5b1e673cec1658afc4ebbbc800213847804facf5)) 696 | - **readme:** http to https ([70946b85](https://github.com/rust-lang/rustlings/commit/70946b85e536e80e70ed9505cb650ca0a3a1fbb5)) 697 | - **test1:** 698 | - Swap assertion parameter order ([4086d463](https://github.com/rust-lang/rustlings/commit/4086d463a981e81d97781851d17db2ced290f446)) 699 | - renamed function name to snake case closes #180 ([89d5186c](https://github.com/rust-lang/rustlings/commit/89d5186c0dae8135ecabf90ee8bb35949bc2d29b)) 700 | 701 | #### Features 702 | 703 | - Add enums exercises ([dc150321](https://github.com/rust-lang/rustlings/commit/dc15032112fc485226a573a18139e5ce928b1755)) 704 | - Added exercise for struct update syntax ([1c4c8764](https://github.com/rust-lang/rustlings/commit/1c4c8764ed118740cd4cee73272ddc6cceb9d959)) 705 | - **iterators2:** adds iterators2 exercise including config ([9288fccf](https://github.com/rust-lang/rustlings/commit/9288fccf07a2c5043b76d0fd6491e4cf72d76031)) 706 | 707 | 708 | 709 | ### 1.4.1 (2019-08-13) 710 | 711 | #### Bug Fixes 712 | 713 | - **iterators2:** Remove syntax resulting in misleading error message ([4cde8664](https://github.com/rust-lang/rustlings/commit/4cde86643e12db162a66e62f23b78962986046ac)) 714 | - **option1:** Add test for prematurely passing exercise ([a750e4a1](https://github.com/rust-lang/rustlings/commit/a750e4a1a3006227292bb17d57d78ce84da6bfc6)) 715 | - **test1:** Swap assertion parameter order ([4086d463](https://github.com/rust-lang/rustlings/commit/4086d463a981e81d97781851d17db2ced290f446)) 716 | 717 | 718 | 719 | ## 1.4.0 (2019-07-13) 720 | 721 | #### Bug Fixes 722 | 723 | - **installation:** Fix rustlings installation check ([7a252c47](https://github.com/rust-lang/rustlings/commit/7a252c475551486efb52f949b8af55803b700bc6)) 724 | - **iterators:** Rename iterator3.rs ([433d2115](https://github.com/rust-lang/rustlings/commit/433d2115bc1c04b6d34a335a18c9a8f3e2672bc6)) 725 | - **readme:** http to https ([70946b85](https://github.com/rust-lang/rustlings/commit/70946b85e536e80e70ed9505cb650ca0a3a1fbb5)) 726 | - **test1:** renamed function name to snake case ([89d5186c](https://github.com/rust-lang/rustlings/commit/89d5186c0dae8135ecabf90ee8bb35949bc2d29b)) 727 | - **cli:** Check if changed exercise file exists before calling verify ([ba85ca3](https://github.com/rust-lang/rustlings/commit/ba85ca32c4cfc61de46851ab89f9c58a28f33c88)) 728 | - **structs1:** Fix the irrefutable let pattern warning ([cc6a141](https://github.com/rust-lang/rustlings/commit/cc6a14104d7c034eadc98297eaaa972d09c50b1f)) 729 | 730 | #### Features 731 | 732 | - **changelog:** Use clog for changelogs ([34e31232](https://github.com/rust-lang/rustlings/commit/34e31232dfddde284a341c9609b33cd27d9d5724)) 733 | - **iterators2:** adds iterators2 exercise including config ([9288fccf](https://github.com/rust-lang/rustlings/commit/9288fccf07a2c5043b76d0fd6491e4cf72d76031)) 734 | 735 | 736 | 737 | ### 1.3.0 (2019-06-05) 738 | 739 | #### Features 740 | 741 | - Adds a simple exercise for structures (#163, @briankung) 742 | 743 | #### Bug Fixes 744 | 745 | - Add Result type signature as it is difficult for new comers to understand Generics and Error all at once. (#157, @veggiemonk) 746 | - Rustfmt and whitespace fixes (#161, @eddyp) 747 | - errorsn.rs: Separate also the hints from each other to avoid accidental viewing (#162, @eddyp) 748 | - fixed outdated links (#165, @gushroom) 749 | - Fix broken link (#164, @HanKruiger) 750 | - Remove highlighting and syntect (#167, @komaeda) 751 | 752 | 753 | 754 | ### 1.2.2 (2019-05-07) 755 | 756 | #### Bug Fixes 757 | 758 | - Reverted `--nocapture` flag since it was causing tests to pass unconditionally 759 | 760 | 761 | 762 | ### 1.2.1 (2019-04-22) 763 | 764 | #### Bug Fixes 765 | 766 | - Fix the `--nocapture` feature (@komaeda) 767 | - Provide a nicer error message for when you're in the wrong directory 768 | 769 | 770 | 771 | ### 1.2.0 (2019-04-22) 772 | 773 | #### Features 774 | 775 | - Add errors to exercises that compile without user changes (@yvan-sraka) 776 | - Use --nocapture when testing, enabling `println!` when running (@komaeda) 777 | 778 | 779 | 780 | ### 1.1.1 (2019-04-14) 781 | 782 | #### Bug fixes 783 | 784 | - Fix permissions on exercise files (@zacanger, #133) 785 | - Make installation checks more thorough (@komaeda, 1b3469f236bc6979c27f6e1a04e4138a88e55de3) 786 | - Fix order of true/false in tests for executables (@mgeier, #137) 787 | - Stop run from panicking when compile fails (@cjpearce, #141) 788 | - Fix intermittent test failure caused by race condition (@cjpearce, #140) 789 | - Fix links by deleting book version (@diodfr, #142) 790 | - Canonicalize paths to fix path matching (@cjpearce, #143) 791 | 792 | 793 | 794 | ### 1.1.0 (2019-03-20) 795 | 796 | - errors2.rs: update link to Rust book (#124) 797 | - Start verification at most recently modified file (#120) 798 | - Watch for file creation events in watch mode (#117) 799 | - Add standard library types to exercises suite (#119) 800 | - Give a warning when Rustlings isn't run from the right directory (#123) 801 | - Verify that rust version is recent enough to install Rustlings (#131) 802 | 803 | 804 | 805 | ### 1.0.1 (2019-03-06) 806 | 807 | - Adds a way to install Rustlings in one command (`curl -L https://git.io/rustlings | bash`) 808 | - Makes `rustlings watch` react to create file events (@shaunbennett, #117) 809 | - Reworks the exercise management to use an external TOML file instead of just listing them in the code 810 | 811 | 812 | 813 | ### 1.0.0 (2019-03-06) 814 | 815 | Initial release. 816 | -------------------------------------------------------------------------------- /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 | ## 2025年春季第一期 Rust 入门训练营-导学阶段基础测试 2 | 3 | **请在完成本实验后凭借排行榜分数截图练习班主任加入学习交流群** 4 | 5 | 导学阶段将通过 Rustlings 进行测试,我们选取了 Rustlings 练习的前16道基础题目进行测试,确保您具备基本的编程能力,请按照以下步骤进行练习: 6 | 7 | 1. 在网络浏览器中用自己的 github id 登录 github.com。 8 | 2. 请将本仓库 fork 到您的 github 账号下,然后参照以下步骤完成环境配置和实验提交: 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/0?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/functions/README.md: -------------------------------------------------------------------------------- 1 | # Functions 2 | 3 | Here, you'll learn how to write functions and how the Rust compiler can help you debug errors even 4 | in more complex code. 5 | 6 | ## Further information 7 | 8 | - [How Functions Work](https://doc.rust-lang.org/book/ch03-03-how-functions-work.html) 9 | -------------------------------------------------------------------------------- /exercises/functions/functions1.rs: -------------------------------------------------------------------------------- 1 | // functions1.rs 2 | // 3 | // Execute `rustlings hint functions1` or use the `hint` watch subcommand for a 4 | // hint. 5 | 6 | // I AM NOT DONE 7 | 8 | fn main() { 9 | call_me(); 10 | } 11 | -------------------------------------------------------------------------------- /exercises/functions/functions2.rs: -------------------------------------------------------------------------------- 1 | // functions2.rs 2 | // 3 | // Execute `rustlings hint functions2` or use the `hint` watch subcommand for a 4 | // hint. 5 | 6 | // I AM NOT DONE 7 | 8 | fn main() { 9 | call_me(3); 10 | } 11 | 12 | fn call_me(num:) { 13 | for i in 0..num { 14 | println!("Ring! Call number {}", i + 1); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /exercises/functions/functions3.rs: -------------------------------------------------------------------------------- 1 | // functions3.rs 2 | // 3 | // Execute `rustlings hint functions3` or use the `hint` watch subcommand for a 4 | // hint. 5 | 6 | // I AM NOT DONE 7 | 8 | fn main() { 9 | call_me(); 10 | } 11 | 12 | fn call_me(num: u32) { 13 | for i in 0..num { 14 | println!("Ring! Call number {}", i + 1); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /exercises/functions/functions4.rs: -------------------------------------------------------------------------------- 1 | // functions4.rs 2 | // 3 | // This store is having a sale where if the price is an even number, you get 10 4 | // Rustbucks off, but if it's an odd number, it's 3 Rustbucks off. (Don't worry 5 | // about the function bodies themselves, we're only interested in the signatures 6 | // for now. If anything, this is a good way to peek ahead to future exercises!) 7 | // 8 | // Execute `rustlings hint functions4` or use the `hint` watch subcommand for a 9 | // hint. 10 | 11 | // I AM NOT DONE 12 | 13 | fn main() { 14 | let original_price = 51; 15 | println!("Your sale price is {}", sale_price(original_price)); 16 | } 17 | 18 | fn sale_price(price: i32) -> { 19 | if is_even(price) { 20 | price - 10 21 | } else { 22 | price - 3 23 | } 24 | } 25 | 26 | fn is_even(num: i32) -> bool { 27 | num % 2 == 0 28 | } 29 | -------------------------------------------------------------------------------- /exercises/functions/functions5.rs: -------------------------------------------------------------------------------- 1 | // functions5.rs 2 | // 3 | // Execute `rustlings hint functions5` or use the `hint` watch subcommand for a 4 | // hint. 5 | 6 | // I AM NOT DONE 7 | 8 | fn main() { 9 | let answer = square(3); 10 | println!("The square of 3 is {}", answer); 11 | } 12 | 13 | fn square(num: i32) -> i32 { 14 | num * num; 15 | } 16 | -------------------------------------------------------------------------------- /exercises/if/README.md: -------------------------------------------------------------------------------- 1 | # If 2 | 3 | `if`, the most basic (but still surprisingly versatile!) type of control flow, is what you'll learn here. 4 | 5 | ## Further information 6 | 7 | - [Control Flow - if expressions](https://doc.rust-lang.org/book/ch03-05-control-flow.html#if-expressions) 8 | -------------------------------------------------------------------------------- /exercises/if/if1.rs: -------------------------------------------------------------------------------- 1 | // if1.rs 2 | // 3 | // Execute `rustlings hint if1` or use the `hint` watch subcommand for a hint. 4 | 5 | // I AM NOT DONE 6 | 7 | pub fn bigger(a: i32, b: i32) -> i32 { 8 | // Complete this function to return the bigger number! 9 | // Do not use: 10 | // - another function call 11 | // - additional variables 12 | } 13 | 14 | // Don't mind this for now :) 15 | #[cfg(test)] 16 | mod tests { 17 | use super::*; 18 | 19 | #[test] 20 | fn ten_is_bigger_than_eight() { 21 | assert_eq!(10, bigger(10, 8)); 22 | } 23 | 24 | #[test] 25 | fn fortytwo_is_bigger_than_thirtytwo() { 26 | assert_eq!(42, bigger(32, 42)); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /exercises/if/if2.rs: -------------------------------------------------------------------------------- 1 | // if2.rs 2 | // 3 | // Step 1: Make me compile! 4 | // Step 2: Get the bar_for_fuzz and default_to_baz tests passing! 5 | // 6 | // Execute `rustlings hint if2` or use the `hint` watch subcommand for a hint. 7 | 8 | // I AM NOT DONE 9 | 10 | pub fn foo_if_fizz(fizzish: &str) -> &str { 11 | if fizzish == "fizz" { 12 | "foo" 13 | } else { 14 | 1 15 | } 16 | } 17 | 18 | // No test changes needed! 19 | #[cfg(test)] 20 | mod tests { 21 | use super::*; 22 | 23 | #[test] 24 | fn foo_for_fizz() { 25 | assert_eq!(foo_if_fizz("fizz"), "foo") 26 | } 27 | 28 | #[test] 29 | fn bar_for_fuzz() { 30 | assert_eq!(foo_if_fizz("fuzz"), "bar") 31 | } 32 | 33 | #[test] 34 | fn default_to_baz() { 35 | assert_eq!(foo_if_fizz("literally anything"), "baz") 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /exercises/if/if3.rs: -------------------------------------------------------------------------------- 1 | // if3.rs 2 | // 3 | // Execute `rustlings hint if3` or use the `hint` watch subcommand for a hint. 4 | 5 | // I AM NOT DONE 6 | 7 | pub fn animal_habitat(animal: &str) -> &'static str { 8 | let identifier = if animal == "crab" { 9 | 1 10 | } else if animal == "gopher" { 11 | 2.0 12 | } else if animal == "snake" { 13 | 3 14 | } else { 15 | "Unknown" 16 | }; 17 | 18 | // DO NOT CHANGE THIS STATEMENT BELOW 19 | let habitat = if identifier == 1 { 20 | "Beach" 21 | } else if identifier == 2 { 22 | "Burrow" 23 | } else if identifier == 3 { 24 | "Desert" 25 | } else { 26 | "Unknown" 27 | }; 28 | 29 | habitat 30 | } 31 | 32 | #[cfg(test)] 33 | mod tests { 34 | use super::*; 35 | 36 | #[test] 37 | fn gopher_lives_in_burrow() { 38 | assert_eq!(animal_habitat("gopher"), "Burrow") 39 | } 40 | 41 | #[test] 42 | fn snake_lives_in_desert() { 43 | assert_eq!(animal_habitat("snake"), "Desert") 44 | } 45 | 46 | #[test] 47 | fn crab_lives_on_beach() { 48 | assert_eq!(animal_habitat("crab"), "Beach") 49 | } 50 | 51 | #[test] 52 | fn unknown_animal() { 53 | assert_eq!(animal_habitat("dinosaur"), "Unknown") 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /exercises/intro/README.md: -------------------------------------------------------------------------------- 1 | # Intro 2 | 3 | Rust uses the `print!` and `println!` macros to print text to the console. 4 | 5 | ## Further information 6 | 7 | - [Hello World](https://doc.rust-lang.org/rust-by-example/hello.html) 8 | - [Formatted print](https://doc.rust-lang.org/rust-by-example/hello/print.html) 9 | -------------------------------------------------------------------------------- /exercises/intro/intro1.rs: -------------------------------------------------------------------------------- 1 | // intro1.rs 2 | // 3 | // About this `I AM NOT DONE` thing: 4 | // We sometimes encourage you to keep trying things on a given exercise, even 5 | // after you already figured it out. If you got everything working and feel 6 | // ready for the next exercise, remove the `I AM NOT DONE` comment below. 7 | // 8 | // If you're running this using `rustlings watch`: The exercise file will be 9 | // reloaded when you change one of the lines below! Try adding a `println!` 10 | // line, or try changing what it outputs in your terminal. Try removing a 11 | // semicolon and see what happens! 12 | // 13 | // Execute `rustlings hint intro1` or use the `hint` watch subcommand for a 14 | // hint. 15 | 16 | 17 | fn main() { 18 | println!("Hello and"); 19 | println!(r#" welcome to... "#); 20 | println!(r#" _ _ _ "#); 21 | println!(r#" _ __ _ _ ___| |_| (_)_ __ __ _ ___ "#); 22 | println!(r#" | '__| | | / __| __| | | '_ \ / _` / __| "#); 23 | println!(r#" | | | |_| \__ \ |_| | | | | | (_| \__ \ "#); 24 | println!(r#" |_| \__,_|___/\__|_|_|_| |_|\__, |___/ "#); 25 | println!(r#" |___/ "#); 26 | println!(); 27 | println!("This exercise compiles successfully. The remaining exercises contain a compiler"); 28 | println!("or logic error. The central concept behind Rustlings is to fix these errors and"); 29 | println!("solve the exercises. Good luck!"); 30 | println!(); 31 | println!("The source for this exercise is in `exercises/intro/intro1.rs`. Have a look!"); 32 | println!( 33 | "Going forward, the source of the exercises will always be in the success/failure output." 34 | ); 35 | println!(); 36 | println!( 37 | "If you want to use rust-analyzer, Rust's LSP implementation, make sure your editor is set" 38 | ); 39 | println!("up, and then run `rustlings lsp` before continuing.") 40 | } 41 | -------------------------------------------------------------------------------- /exercises/intro/intro2.rs: -------------------------------------------------------------------------------- 1 | // intro2.rs 2 | // 3 | // Make the code print a greeting to the world. 4 | // 5 | // Execute `rustlings hint intro2` or use the `hint` watch subcommand for a 6 | // hint. 7 | 8 | // I AM NOT DONE 9 | 10 | fn main() { 11 | println!("Hello {}!"); 12 | } 13 | -------------------------------------------------------------------------------- /exercises/quiz1.rs: -------------------------------------------------------------------------------- 1 | // quiz1.rs 2 | // 3 | // This is a quiz for the following sections: 4 | // - Variables 5 | // - Functions 6 | // - If 7 | // 8 | // Mary is buying apples. The price of an apple is calculated as follows: 9 | // - An apple costs 2 rustbucks. 10 | // - If Mary buys more than 40 apples, each apple only costs 1 rustbuck! 11 | // Write a function that calculates the price of an order of apples given the 12 | // quantity bought. No hints this time! 13 | // 14 | // No hints this time ;) 15 | 16 | // I AM NOT DONE 17 | 18 | // Put your function here! 19 | // fn calculate_price_of_apples { 20 | 21 | // Don't modify this function! 22 | #[test] 23 | fn verify_test() { 24 | let price1 = calculate_price_of_apples(35); 25 | let price2 = calculate_price_of_apples(40); 26 | let price3 = calculate_price_of_apples(41); 27 | let price4 = calculate_price_of_apples(65); 28 | 29 | assert_eq!(70, price1); 30 | assert_eq!(80, price2); 31 | assert_eq!(41, price3); 32 | assert_eq!(65, price4); 33 | } 34 | -------------------------------------------------------------------------------- /exercises/variables/README.md: -------------------------------------------------------------------------------- 1 | # Variables 2 | 3 | In Rust, variables are immutable by default. 4 | When a variable is immutable, once a value is bound to a name, you can’t change that value. 5 | You can make them mutable by adding `mut` in front of the variable name. 6 | 7 | ## Further information 8 | 9 | - [Variables and Mutability](https://doc.rust-lang.org/book/ch03-01-variables-and-mutability.html) 10 | -------------------------------------------------------------------------------- /exercises/variables/variables1.rs: -------------------------------------------------------------------------------- 1 | // variables1.rs 2 | // 3 | // Make me compile! 4 | // 5 | // Execute `rustlings hint variables1` or use the `hint` watch subcommand for a 6 | // hint. 7 | 8 | // I AM NOT DONE 9 | 10 | fn main() { 11 | x = 5; 12 | println!("x has the value {}", x); 13 | } 14 | -------------------------------------------------------------------------------- /exercises/variables/variables2.rs: -------------------------------------------------------------------------------- 1 | // variables2.rs 2 | // 3 | // Execute `rustlings hint variables2` or use the `hint` watch subcommand for a 4 | // hint. 5 | 6 | // I AM NOT DONE 7 | 8 | fn main() { 9 | let x; 10 | if x == 10 { 11 | println!("x is ten!"); 12 | } else { 13 | println!("x is not ten!"); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /exercises/variables/variables3.rs: -------------------------------------------------------------------------------- 1 | // variables3.rs 2 | // 3 | // Execute `rustlings hint variables3` or use the `hint` watch subcommand for a 4 | // hint. 5 | 6 | // I AM NOT DONE 7 | 8 | fn main() { 9 | let x: i32; 10 | println!("Number {}", x); 11 | } 12 | -------------------------------------------------------------------------------- /exercises/variables/variables4.rs: -------------------------------------------------------------------------------- 1 | // variables4.rs 2 | // 3 | // Execute `rustlings hint variables4` or use the `hint` watch subcommand for a 4 | // hint. 5 | 6 | // I AM NOT DONE 7 | 8 | fn main() { 9 | let x = 3; 10 | println!("Number {}", x); 11 | x = 5; // don't change this line 12 | println!("Number {}", x); 13 | } 14 | -------------------------------------------------------------------------------- /exercises/variables/variables5.rs: -------------------------------------------------------------------------------- 1 | // variables5.rs 2 | // 3 | // Execute `rustlings hint variables5` or use the `hint` watch subcommand for a 4 | // hint. 5 | 6 | // I AM NOT DONE 7 | 8 | fn main() { 9 | let number = "T-H-R-E-E"; // don't change this line 10 | println!("Spell a Number : {}", number); 11 | number = 3; // don't rename this variable 12 | println!("Number plus two is : {}", number + 2); 13 | } 14 | -------------------------------------------------------------------------------- /exercises/variables/variables6.rs: -------------------------------------------------------------------------------- 1 | // variables6.rs 2 | // 3 | // Execute `rustlings hint variables6` or use the `hint` watch subcommand for a 4 | // hint. 5 | 6 | // I AM NOT DONE 7 | 8 | const NUMBER = 3; 9 | fn main() { 10 | println!("Number {}", NUMBER); 11 | } 12 | -------------------------------------------------------------------------------- /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 | [[exercises]] 12 | name = "intro2" 13 | path = "exercises/intro/intro2.rs" 14 | mode = "compile" 15 | hint = """ 16 | Add an argument after the format string.""" 17 | 18 | # VARIABLES 19 | 20 | [[exercises]] 21 | name = "variables1" 22 | path = "exercises/variables/variables1.rs" 23 | mode = "compile" 24 | hint = """ 25 | The declaration on line 8 is missing a keyword that is needed in Rust 26 | to create a new variable binding.""" 27 | 28 | [[exercises]] 29 | name = "variables2" 30 | path = "exercises/variables/variables2.rs" 31 | mode = "compile" 32 | hint = """ 33 | The compiler message is saying that Rust cannot infer the type that the 34 | variable binding `x` has with what is given here. 35 | What happens if you annotate line 7 with a type annotation? 36 | What if you give x a value? 37 | What if you do both? 38 | What type should x be, anyway? 39 | What if x is the same type as 10? What if it's a different type?""" 40 | 41 | [[exercises]] 42 | name = "variables3" 43 | path = "exercises/variables/variables3.rs" 44 | mode = "compile" 45 | hint = """ 46 | Oops! In this exercise, we have a variable binding that we've created on 47 | line 7, and we're trying to use it on line 8, but we haven't given it a 48 | value. We can't print out something that isn't there; try giving x a value! 49 | This is an error that can cause bugs that's very easy to make in any 50 | programming language -- thankfully the Rust compiler has caught this for us!""" 51 | 52 | [[exercises]] 53 | name = "variables4" 54 | path = "exercises/variables/variables4.rs" 55 | mode = "compile" 56 | hint = """ 57 | In Rust, variable bindings are immutable by default. But here we're trying 58 | to reassign a different value to x! There's a keyword we can use to make 59 | a variable binding mutable instead.""" 60 | 61 | [[exercises]] 62 | name = "variables5" 63 | path = "exercises/variables/variables5.rs" 64 | mode = "compile" 65 | hint = """ 66 | In variables4 we already learned how to make an immutable variable mutable 67 | using a special keyword. Unfortunately this doesn't help us much in this exercise 68 | because we want to assign a different typed value to an existing variable. Sometimes 69 | you may also like to reuse existing variable names because you are just converting 70 | values to different types like in this exercise. 71 | Fortunately Rust has a powerful solution to this problem: 'Shadowing'! 72 | You can read more about 'Shadowing' in the book's section 'Variables and Mutability': 73 | https://doc.rust-lang.org/book/ch03-01-variables-and-mutability.html#shadowing 74 | Try to solve this exercise afterwards using this technique.""" 75 | 76 | [[exercises]] 77 | name = "variables6" 78 | path = "exercises/variables/variables6.rs" 79 | mode = "compile" 80 | hint = """ 81 | We know about variables and mutability, but there is another important type of 82 | variable available: constants. 83 | Constants are always immutable and they are declared with keyword 'const' rather 84 | than keyword 'let'. 85 | Constants types must also always be annotated. 86 | 87 | Read more about constants and the differences between variables and constants under 'Constants' in the book's section 'Variables and Mutability': 88 | https://doc.rust-lang.org/book/ch03-01-variables-and-mutability.html#constants 89 | """ 90 | 91 | # FUNCTIONS 92 | 93 | [[exercises]] 94 | name = "functions1" 95 | path = "exercises/functions/functions1.rs" 96 | mode = "compile" 97 | hint = """ 98 | This main function is calling a function that it expects to exist, but the 99 | function doesn't exist. It expects this function to have the name `call_me`. 100 | It expects this function to not take any arguments and not return a value. 101 | Sounds a lot like `main`, doesn't it?""" 102 | 103 | [[exercises]] 104 | name = "functions2" 105 | path = "exercises/functions/functions2.rs" 106 | mode = "compile" 107 | hint = """ 108 | Rust requires that all parts of a function's signature have type annotations, 109 | but `call_me` is missing the type annotation of `num`.""" 110 | 111 | [[exercises]] 112 | name = "functions3" 113 | path = "exercises/functions/functions3.rs" 114 | mode = "compile" 115 | hint = """ 116 | This time, the function *declaration* is okay, but there's something wrong 117 | with the place where we're calling the function. 118 | As a reminder, you can freely play around with different solutions in Rustlings! 119 | Watch mode will only jump to the next exercise if you remove the I AM NOT DONE comment.""" 120 | 121 | [[exercises]] 122 | name = "functions4" 123 | path = "exercises/functions/functions4.rs" 124 | mode = "compile" 125 | hint = """ 126 | The error message points to line 17 and says it expects a type after the 127 | `->`. This is where the function's return type should be -- take a look at 128 | the `is_even` function for an example! 129 | 130 | Also: Did you figure out that, technically, u32 would be the more fitting type 131 | for the prices here, since they can't be negative? If so, kudos!""" 132 | 133 | [[exercises]] 134 | name = "functions5" 135 | path = "exercises/functions/functions5.rs" 136 | mode = "compile" 137 | hint = """ 138 | This is a really common error that can be fixed by removing one character. 139 | It happens because Rust distinguishes between expressions and statements: expressions return a value based on their operand(s), and statements simply return a () type which behaves just like `void` in C/C++ language. 140 | We want to return a value of `i32` type from the `square` function, but it is returning a `()` type... 141 | They are not the same. There are two solutions: 142 | 1. Add a `return` ahead of `num * num;` 143 | 2. remove `;`, make it to be `num * num`""" 144 | 145 | # IF 146 | 147 | [[exercises]] 148 | name = "if1" 149 | path = "exercises/if/if1.rs" 150 | mode = "test" 151 | hint = """ 152 | It's possible to do this in one line if you would like! 153 | Some similar examples from other languages: 154 | - In C(++) this would be: `a > b ? a : b` 155 | - In Python this would be: `a if a > b else b` 156 | Remember in Rust that: 157 | - the `if` condition does not need to be surrounded by parentheses 158 | - `if`/`else` conditionals are expressions 159 | - Each condition is followed by a `{}` block.""" 160 | 161 | [[exercises]] 162 | name = "if2" 163 | path = "exercises/if/if2.rs" 164 | mode = "test" 165 | hint = """ 166 | For that first compiler error, it's important in Rust that each conditional 167 | block returns the same type! To get the tests passing, you will need a couple 168 | conditions checking different input values.""" 169 | 170 | [[exercises]] 171 | name = "if3" 172 | path = "exercises/if/if3.rs" 173 | mode = "test" 174 | hint = """ 175 | In Rust, every arm of an `if` expression has to return the same type of value. Make sure the type is consistent across all arms.""" 176 | 177 | # QUIZ 1 178 | 179 | [[exercises]] 180 | name = "quiz1" 181 | path = "exercises/quiz1.rs" 182 | mode = "test" 183 | hint = "No hints this time ;)" -------------------------------------------------------------------------------- /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 | --------------------------------------------------------------------------------