├── .cargo └── config.toml ├── .gitattributes ├── .github ├── FUNDING.yml └── workflows │ └── ci.yml ├── .gitignore ├── .gitlab-ci.yml ├── Cargo.toml ├── LICENSE-CC-BY-SA ├── README.md ├── docs ├── 0 │ └── index.html ├── 1 │ └── index.html ├── 2 │ └── index.html ├── 3 │ └── index.html ├── 4 │ └── index.html ├── 5 │ └── index.html ├── 6 │ └── index.html ├── 7 │ └── index.html ├── 8 │ └── index.html ├── 9 │ └── index.html ├── 10 │ └── index.html ├── 11 │ └── index.html ├── 12 │ └── index.html ├── 13 │ └── index.html ├── 14 │ └── index.html ├── 15 │ └── index.html ├── 16 │ └── index.html ├── 17 │ └── index.html ├── 18 │ └── index.html ├── 19 │ └── index.html ├── 20 │ └── index.html ├── 21 │ └── index.html ├── 22 │ └── index.html ├── 23 │ └── index.html ├── 24 │ └── index.html ├── 25 │ └── index.html ├── 26 │ └── index.html ├── 27 │ └── index.html ├── 28 │ └── index.html ├── 29 │ └── index.html ├── 30 │ └── index.html ├── 31 │ └── index.html ├── 32 │ └── index.html ├── 33 │ └── index.html ├── 34 │ └── index.html ├── 35 │ └── index.html ├── 36 │ └── index.html ├── 37 │ └── index.html ├── index.html ├── questions.js ├── quiz.css └── quiz.js ├── eslint.config.mjs ├── package.json ├── questions ├── 001-macro-count-statements.md ├── 001-macro-count-statements.rs ├── 002-bitand-or-reference.md ├── 002-bitand-or-reference.rs ├── 003-mutate-const.md ├── 003-mutate-const.rs ├── 004-dotdot-in-tuple.md ├── 004-dotdot-in-tuple.rs ├── 005-trait-resolution-hrtb.md ├── 005-trait-resolution-hrtb.rs ├── 006-value-of-assignment.md ├── 006-value-of-assignment.rs ├── 007-surprise-wildcard-match.md ├── 007-surprise-wildcard-match.rs ├── 008-tokenize-punctuation.md ├── 008-tokenize-punctuation.rs ├── 009-opaque-metavariable.md ├── 009-opaque-metavariable.rs ├── 010-shadowed-trait-object-method.md ├── 010-shadowed-trait-object-method.rs ├── 011-function-pointer-comparison.md ├── 011-function-pointer-comparison.rs ├── 012-binding-drop-behavior.md ├── 012-binding-drop-behavior.rs ├── 013-mutable-zst.md ├── 013-mutable-zst.rs ├── 014-trait-autoref.md ├── 014-trait-autoref.rs ├── 015-inference-of-number-type.md ├── 015-inference-of-number-type.rs ├── 016-prefix-decrement.md ├── 016-prefix-decrement.rs ├── 017-unary-decrement.md ├── 017-unary-decrement.rs ├── 018-method-or-function-pointer.md ├── 018-method-or-function-pointer.rs ├── 019-dropped-by-underscore.md ├── 019-dropped-by-underscore.rs ├── 020-break-return-in-condition.md ├── 020-break-return-in-condition.rs ├── 021-closure-or-logical-or.md ├── 021-closure-or-logical-or.rs ├── 022-macro-tokenize-number.md ├── 022-macro-tokenize-number.rs ├── 023-inherent-vs-trait-method.md ├── 023-inherent-vs-trait-method.rs ├── 024-local-and-const-hygiene.md ├── 024-local-and-const-hygiene.rs ├── 025-unit-infallible-match.md ├── 025-unit-infallible-match.rs ├── 026-iterator-lazy-map.md ├── 026-iterator-lazy-map.rs ├── 027-subtrait-dispatch.md ├── 027-subtrait-dispatch.rs ├── 028-underscore-prefix.md ├── 028-underscore-prefix.rs ├── 029-tuple-trailing-commas.md ├── 029-tuple-trailing-commas.rs ├── 030-clone-pointers.md ├── 030-clone-pointers.rs ├── 031-method-lookup.md ├── 031-method-lookup.rs ├── 032-or-pattern-guard.md ├── 032-or-pattern-guard.rs ├── 033-range-full-method.md ├── 033-range-full-method.rs ├── 034-fn-pointer-vs-fn-type.md ├── 034-fn-pointer-vs-fn-type.rs ├── 035-decl-macro-hygiene.md ├── 035-decl-macro-hygiene.rs ├── 036-fnmut-copy.md ├── 036-fnmut-copy.rs ├── 037-lifetime-extension.md └── 037-lifetime-extension.rs ├── rust-quiz ├── screenshot.png └── src ├── error.rs ├── main.rs ├── render.rs └── serve.rs /.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [alias] 2 | serve = "run -- serve" 3 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | docs/questions.js linguist-generated 2 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: dtolnay 2 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | pull_request: 6 | workflow_dispatch: 7 | schedule: [cron: "40 1 * * *"] 8 | 9 | permissions: 10 | contents: read 11 | 12 | env: 13 | RUSTFLAGS: -Dwarnings 14 | 15 | jobs: 16 | pre_ci: 17 | uses: dtolnay/.github/.github/workflows/pre_ci.yml@master 18 | 19 | test: 20 | name: Check answers 21 | needs: pre_ci 22 | if: needs.pre_ci.outputs.continue 23 | runs-on: ubuntu-latest 24 | timeout-minutes: 45 25 | steps: 26 | - uses: actions/checkout@v4 27 | - uses: dtolnay/rust-toolchain@nightly 28 | - name: Check answers 29 | run: | 30 | rm -f questions/000-* 31 | cargo run 32 | git checkout -- questions 33 | git diff --exit-code 34 | 35 | check: 36 | name: Rust ${{matrix.rust}} 37 | needs: pre_ci 38 | if: needs.pre_ci.outputs.continue 39 | runs-on: ubuntu-latest 40 | strategy: 41 | fail-fast: false 42 | matrix: 43 | rust: [beta, stable] 44 | timeout-minutes: 45 45 | steps: 46 | - uses: actions/checkout@v4 47 | - uses: dtolnay/rust-toolchain@master 48 | with: 49 | toolchain: ${{matrix.rust}} 50 | - run: cargo check 51 | 52 | eslint: 53 | name: ESLint 54 | runs-on: ubuntu-latest 55 | if: github.event_name != 'pull_request' 56 | timeout-minutes: 45 57 | steps: 58 | - uses: actions/checkout@v4 59 | - run: npm install 60 | - run: npx eslint 61 | 62 | clippy: 63 | name: Clippy 64 | runs-on: ubuntu-latest 65 | if: github.event_name != 'pull_request' 66 | timeout-minutes: 45 67 | steps: 68 | - uses: actions/checkout@v4 69 | - uses: dtolnay/rust-toolchain@clippy 70 | - run: cargo clippy -- -Dclippy::all -Dclippy::pedantic 71 | 72 | outdated: 73 | name: Outdated 74 | runs-on: ubuntu-latest 75 | if: github.event_name != 'pull_request' 76 | timeout-minutes: 45 77 | steps: 78 | - uses: actions/checkout@v4 79 | - uses: dtolnay/rust-toolchain@stable 80 | - uses: dtolnay/install@cargo-outdated 81 | - run: cargo outdated --workspace --exit-code 1 82 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /Cargo.lock 2 | /node_modules/ 3 | /package-lock.json 4 | /target/ 5 | -------------------------------------------------------------------------------- /.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | image: alpine:latest 2 | 3 | pages: 4 | stage: deploy 5 | script: 6 | - echo static html 7 | artifacts: 8 | paths: 9 | - public 10 | only: 11 | - master 12 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rust-quiz" 3 | version = "0.0.6" 4 | authors = ["David Tolnay "] 5 | description = "Medium to hard Rust questions with complete explanations" 6 | edition = "2021" 7 | exclude = ["rust-quiz"] 8 | homepage = "https://dtolnay.github.io/rust-quiz" 9 | license = "CC-BY-SA-4.0" 10 | publish = false 11 | repository = "https://github.com/dtolnay/rust-quiz" 12 | 13 | [dependencies] 14 | clap = { version = "4", features = ["deprecated", "derive"] } 15 | futures = "0.3" 16 | http = "1" 17 | hyper = { version = "1", features = ["http1", "http2", "server"] } 18 | hyper-staticfile = "0.10" 19 | hyper-util = { version = "0.1", features = ["tokio"] } 20 | num_cpus = "1.0" 21 | oqueue = "0.1" 22 | parking_lot = "0.12" 23 | pin-project = "1.0" 24 | pulldown-cmark = "0.13" 25 | rayon = "1.0" 26 | regex = "1.0" 27 | remain = "0.2" 28 | serde = "1.0" 29 | serde_json = "1.0" 30 | thiserror = "2" 31 | tokio = { version = "1.0", features = ["full", "macros"] } 32 | 33 | [package.metadata.docs.rs] 34 | targets = ["x86_64-unknown-linux-gnu"] 35 | -------------------------------------------------------------------------------- /LICENSE-CC-BY-SA: -------------------------------------------------------------------------------- 1 | Attribution-ShareAlike 4.0 International 2 | 3 | ======================================================================= 4 | 5 | Creative Commons Corporation ("Creative Commons") is not a law firm and 6 | does not provide legal services or legal advice. Distribution of 7 | Creative Commons public licenses does not create a lawyer-client or 8 | other relationship. Creative Commons makes its licenses and related 9 | information available on an "as-is" basis. Creative Commons gives no 10 | warranties regarding its licenses, any material licensed under their 11 | terms and conditions, or any related information. Creative Commons 12 | disclaims all liability for damages resulting from their use to the 13 | fullest extent possible. 14 | 15 | Using Creative Commons Public Licenses 16 | 17 | Creative Commons public licenses provide a standard set of terms and 18 | conditions that creators and other rights holders may use to share 19 | original works of authorship and other material subject to copyright 20 | and certain other rights specified in the public license below. The 21 | following considerations are for informational purposes only, are not 22 | exhaustive, and do not form part of our licenses. 23 | 24 | Considerations for licensors: Our public licenses are 25 | intended for use by those authorized to give the public 26 | permission to use material in ways otherwise restricted by 27 | copyright and certain other rights. Our licenses are 28 | irrevocable. Licensors should read and understand the terms 29 | and conditions of the license they choose before applying it. 30 | Licensors should also secure all rights necessary before 31 | applying our licenses so that the public can reuse the 32 | material as expected. Licensors should clearly mark any 33 | material not subject to the license. This includes other CC- 34 | licensed material, or material used under an exception or 35 | limitation to copyright. More considerations for licensors: 36 | wiki.creativecommons.org/Considerations_for_licensors 37 | 38 | Considerations for the public: By using one of our public 39 | licenses, a licensor grants the public permission to use the 40 | licensed material under specified terms and conditions. If 41 | the licensor's permission is not necessary for any reason--for 42 | example, because of any applicable exception or limitation to 43 | copyright--then that use is not regulated by the license. Our 44 | licenses grant only permissions under copyright and certain 45 | other rights that a licensor has authority to grant. Use of 46 | the licensed material may still be restricted for other 47 | reasons, including because others have copyright or other 48 | rights in the material. A licensor may make special requests, 49 | such as asking that all changes be marked or described. 50 | Although not required by our licenses, you are encouraged to 51 | respect those requests where reasonable. More considerations 52 | for the public: 53 | wiki.creativecommons.org/Considerations_for_licensees 54 | 55 | ======================================================================= 56 | 57 | Creative Commons Attribution-ShareAlike 4.0 International Public 58 | License 59 | 60 | By exercising the Licensed Rights (defined below), You accept and agree 61 | to be bound by the terms and conditions of this Creative Commons 62 | Attribution-ShareAlike 4.0 International Public License ("Public 63 | License"). To the extent this Public License may be interpreted as a 64 | contract, You are granted the Licensed Rights in consideration of Your 65 | acceptance of these terms and conditions, and the Licensor grants You 66 | such rights in consideration of benefits the Licensor receives from 67 | making the Licensed Material available under these terms and 68 | conditions. 69 | 70 | 71 | Section 1 -- Definitions. 72 | 73 | a. Adapted Material means material subject to Copyright and Similar 74 | Rights that is derived from or based upon the Licensed Material 75 | and in which the Licensed Material is translated, altered, 76 | arranged, transformed, or otherwise modified in a manner requiring 77 | permission under the Copyright and Similar Rights held by the 78 | Licensor. For purposes of this Public License, where the Licensed 79 | Material is a musical work, performance, or sound recording, 80 | Adapted Material is always produced where the Licensed Material is 81 | synched in timed relation with a moving image. 82 | 83 | b. Adapter's License means the license You apply to Your Copyright 84 | and Similar Rights in Your contributions to Adapted Material in 85 | accordance with the terms and conditions of this Public License. 86 | 87 | c. BY-SA Compatible License means a license listed at 88 | creativecommons.org/compatiblelicenses, approved by Creative 89 | Commons as essentially the equivalent of this Public License. 90 | 91 | d. Copyright and Similar Rights means copyright and/or similar rights 92 | closely related to copyright including, without limitation, 93 | performance, broadcast, sound recording, and Sui Generis Database 94 | Rights, without regard to how the rights are labeled or 95 | categorized. For purposes of this Public License, the rights 96 | specified in Section 2(b)(1)-(2) are not Copyright and Similar 97 | Rights. 98 | 99 | e. Effective Technological Measures means those measures that, in the 100 | absence of proper authority, may not be circumvented under laws 101 | fulfilling obligations under Article 11 of the WIPO Copyright 102 | Treaty adopted on December 20, 1996, and/or similar international 103 | agreements. 104 | 105 | f. Exceptions and Limitations means fair use, fair dealing, and/or 106 | any other exception or limitation to Copyright and Similar Rights 107 | that applies to Your use of the Licensed Material. 108 | 109 | g. License Elements means the license attributes listed in the name 110 | of a Creative Commons Public License. The License Elements of this 111 | Public License are Attribution and ShareAlike. 112 | 113 | h. Licensed Material means the artistic or literary work, database, 114 | or other material to which the Licensor applied this Public 115 | License. 116 | 117 | i. Licensed Rights means the rights granted to You subject to the 118 | terms and conditions of this Public License, which are limited to 119 | all Copyright and Similar Rights that apply to Your use of the 120 | Licensed Material and that the Licensor has authority to license. 121 | 122 | j. Licensor means the individual(s) or entity(ies) granting rights 123 | under this Public License. 124 | 125 | k. Share means to provide material to the public by any means or 126 | process that requires permission under the Licensed Rights, such 127 | as reproduction, public display, public performance, distribution, 128 | dissemination, communication, or importation, and to make material 129 | available to the public including in ways that members of the 130 | public may access the material from a place and at a time 131 | individually chosen by them. 132 | 133 | l. Sui Generis Database Rights means rights other than copyright 134 | resulting from Directive 96/9/EC of the European Parliament and of 135 | the Council of 11 March 1996 on the legal protection of databases, 136 | as amended and/or succeeded, as well as other essentially 137 | equivalent rights anywhere in the world. 138 | 139 | m. You means the individual or entity exercising the Licensed Rights 140 | under this Public License. Your has a corresponding meaning. 141 | 142 | 143 | Section 2 -- Scope. 144 | 145 | a. License grant. 146 | 147 | 1. Subject to the terms and conditions of this Public License, 148 | the Licensor hereby grants You a worldwide, royalty-free, 149 | non-sublicensable, non-exclusive, irrevocable license to 150 | exercise the Licensed Rights in the Licensed Material to: 151 | 152 | a. reproduce and Share the Licensed Material, in whole or 153 | in part; and 154 | 155 | b. produce, reproduce, and Share Adapted Material. 156 | 157 | 2. Exceptions and Limitations. For the avoidance of doubt, where 158 | Exceptions and Limitations apply to Your use, this Public 159 | License does not apply, and You do not need to comply with 160 | its terms and conditions. 161 | 162 | 3. Term. The term of this Public License is specified in Section 163 | 6(a). 164 | 165 | 4. Media and formats; technical modifications allowed. The 166 | Licensor authorizes You to exercise the Licensed Rights in 167 | all media and formats whether now known or hereafter created, 168 | and to make technical modifications necessary to do so. The 169 | Licensor waives and/or agrees not to assert any right or 170 | authority to forbid You from making technical modifications 171 | necessary to exercise the Licensed Rights, including 172 | technical modifications necessary to circumvent Effective 173 | Technological Measures. For purposes of this Public License, 174 | simply making modifications authorized by this Section 2(a) 175 | (4) never produces Adapted Material. 176 | 177 | 5. Downstream recipients. 178 | 179 | a. Offer from the Licensor -- Licensed Material. Every 180 | recipient of the Licensed Material automatically 181 | receives an offer from the Licensor to exercise the 182 | Licensed Rights under the terms and conditions of this 183 | Public License. 184 | 185 | b. Additional offer from the Licensor -- Adapted Material. 186 | Every recipient of Adapted Material from You 187 | automatically receives an offer from the Licensor to 188 | exercise the Licensed Rights in the Adapted Material 189 | under the conditions of the Adapter's License You apply. 190 | 191 | c. No downstream restrictions. You may not offer or impose 192 | any additional or different terms or conditions on, or 193 | apply any Effective Technological Measures to, the 194 | Licensed Material if doing so restricts exercise of the 195 | Licensed Rights by any recipient of the Licensed 196 | Material. 197 | 198 | 6. No endorsement. Nothing in this Public License constitutes or 199 | may be construed as permission to assert or imply that You 200 | are, or that Your use of the Licensed Material is, connected 201 | with, or sponsored, endorsed, or granted official status by, 202 | the Licensor or others designated to receive attribution as 203 | provided in Section 3(a)(1)(A)(i). 204 | 205 | b. Other rights. 206 | 207 | 1. Moral rights, such as the right of integrity, are not 208 | licensed under this Public License, nor are publicity, 209 | privacy, and/or other similar personality rights; however, to 210 | the extent possible, the Licensor waives and/or agrees not to 211 | assert any such rights held by the Licensor to the limited 212 | extent necessary to allow You to exercise the Licensed 213 | Rights, but not otherwise. 214 | 215 | 2. Patent and trademark rights are not licensed under this 216 | Public License. 217 | 218 | 3. To the extent possible, the Licensor waives any right to 219 | collect royalties from You for the exercise of the Licensed 220 | Rights, whether directly or through a collecting society 221 | under any voluntary or waivable statutory or compulsory 222 | licensing scheme. In all other cases the Licensor expressly 223 | reserves any right to collect such royalties. 224 | 225 | 226 | Section 3 -- License Conditions. 227 | 228 | Your exercise of the Licensed Rights is expressly made subject to the 229 | following conditions. 230 | 231 | a. Attribution. 232 | 233 | 1. If You Share the Licensed Material (including in modified 234 | form), You must: 235 | 236 | a. retain the following if it is supplied by the Licensor 237 | with the Licensed Material: 238 | 239 | i. identification of the creator(s) of the Licensed 240 | Material and any others designated to receive 241 | attribution, in any reasonable manner requested by 242 | the Licensor (including by pseudonym if 243 | designated); 244 | 245 | ii. a copyright notice; 246 | 247 | iii. a notice that refers to this Public License; 248 | 249 | iv. a notice that refers to the disclaimer of 250 | warranties; 251 | 252 | v. a URI or hyperlink to the Licensed Material to the 253 | extent reasonably practicable; 254 | 255 | b. indicate if You modified the Licensed Material and 256 | retain an indication of any previous modifications; and 257 | 258 | c. indicate the Licensed Material is licensed under this 259 | Public License, and include the text of, or the URI or 260 | hyperlink to, this Public License. 261 | 262 | 2. You may satisfy the conditions in Section 3(a)(1) in any 263 | reasonable manner based on the medium, means, and context in 264 | which You Share the Licensed Material. For example, it may be 265 | reasonable to satisfy the conditions by providing a URI or 266 | hyperlink to a resource that includes the required 267 | information. 268 | 269 | 3. If requested by the Licensor, You must remove any of the 270 | information required by Section 3(a)(1)(A) to the extent 271 | reasonably practicable. 272 | 273 | b. ShareAlike. 274 | 275 | In addition to the conditions in Section 3(a), if You Share 276 | Adapted Material You produce, the following conditions also apply. 277 | 278 | 1. The Adapter's License You apply must be a Creative Commons 279 | license with the same License Elements, this version or 280 | later, or a BY-SA Compatible License. 281 | 282 | 2. You must include the text of, or the URI or hyperlink to, the 283 | Adapter's License You apply. You may satisfy this condition 284 | in any reasonable manner based on the medium, means, and 285 | context in which You Share Adapted Material. 286 | 287 | 3. You may not offer or impose any additional or different terms 288 | or conditions on, or apply any Effective Technological 289 | Measures to, Adapted Material that restrict exercise of the 290 | rights granted under the Adapter's License You apply. 291 | 292 | 293 | Section 4 -- Sui Generis Database Rights. 294 | 295 | Where the Licensed Rights include Sui Generis Database Rights that 296 | apply to Your use of the Licensed Material: 297 | 298 | a. for the avoidance of doubt, Section 2(a)(1) grants You the right 299 | to extract, reuse, reproduce, and Share all or a substantial 300 | portion of the contents of the database; 301 | 302 | b. if You include all or a substantial portion of the database 303 | contents in a database in which You have Sui Generis Database 304 | Rights, then the database in which You have Sui Generis Database 305 | Rights (but not its individual contents) is Adapted Material, 306 | 307 | including for purposes of Section 3(b); and 308 | c. You must comply with the conditions in Section 3(a) if You Share 309 | all or a substantial portion of the contents of the database. 310 | 311 | For the avoidance of doubt, this Section 4 supplements and does not 312 | replace Your obligations under this Public License where the Licensed 313 | Rights include other Copyright and Similar Rights. 314 | 315 | 316 | Section 5 -- Disclaimer of Warranties and Limitation of Liability. 317 | 318 | a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE 319 | EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS 320 | AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF 321 | ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS, 322 | IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION, 323 | WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR 324 | PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS, 325 | ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT 326 | KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT 327 | ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU. 328 | 329 | b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE 330 | TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION, 331 | NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT, 332 | INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES, 333 | COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR 334 | USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN 335 | ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR 336 | DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR 337 | IN PART, THIS LIMITATION MAY NOT APPLY TO YOU. 338 | 339 | c. The disclaimer of warranties and limitation of liability provided 340 | above shall be interpreted in a manner that, to the extent 341 | possible, most closely approximates an absolute disclaimer and 342 | waiver of all liability. 343 | 344 | 345 | Section 6 -- Term and Termination. 346 | 347 | a. This Public License applies for the term of the Copyright and 348 | Similar Rights licensed here. However, if You fail to comply with 349 | this Public License, then Your rights under this Public License 350 | terminate automatically. 351 | 352 | b. Where Your right to use the Licensed Material has terminated under 353 | Section 6(a), it reinstates: 354 | 355 | 1. automatically as of the date the violation is cured, provided 356 | it is cured within 30 days of Your discovery of the 357 | violation; or 358 | 359 | 2. upon express reinstatement by the Licensor. 360 | 361 | For the avoidance of doubt, this Section 6(b) does not affect any 362 | right the Licensor may have to seek remedies for Your violations 363 | of this Public License. 364 | 365 | c. For the avoidance of doubt, the Licensor may also offer the 366 | Licensed Material under separate terms or conditions or stop 367 | distributing the Licensed Material at any time; however, doing so 368 | will not terminate this Public License. 369 | 370 | d. Sections 1, 5, 6, 7, and 8 survive termination of this Public 371 | License. 372 | 373 | 374 | Section 7 -- Other Terms and Conditions. 375 | 376 | a. The Licensor shall not be bound by any additional or different 377 | terms or conditions communicated by You unless expressly agreed. 378 | 379 | b. Any arrangements, understandings, or agreements regarding the 380 | Licensed Material not stated herein are separate from and 381 | independent of the terms and conditions of this Public License. 382 | 383 | 384 | Section 8 -- Interpretation. 385 | 386 | a. For the avoidance of doubt, this Public License does not, and 387 | shall not be interpreted to, reduce, limit, restrict, or impose 388 | conditions on any use of the Licensed Material that could lawfully 389 | be made without permission under this Public License. 390 | 391 | b. To the extent possible, if any provision of this Public License is 392 | deemed unenforceable, it shall be automatically reformed to the 393 | minimum extent necessary to make it enforceable. If the provision 394 | cannot be reformed, it shall be severed from this Public License 395 | without affecting the enforceability of the remaining terms and 396 | conditions. 397 | 398 | c. No term or condition of this Public License will be waived and no 399 | failure to comply consented to unless expressly agreed to by the 400 | Licensor. 401 | 402 | d. Nothing in this Public License constitutes or may be interpreted 403 | as a limitation upon, or waiver of, any privileges and immunities 404 | that apply to the Licensor or You, including from the legal 405 | processes of any jurisdiction or authority. 406 | 407 | 408 | ======================================================================= 409 | 410 | Creative Commons is not a party to its public 411 | licenses. Notwithstanding, Creative Commons may elect to apply one of 412 | its public licenses to material it publishes and in those instances 413 | will be considered the “Licensor.” The text of the Creative Commons 414 | public licenses is dedicated to the public domain under the CC0 Public 415 | Domain Dedication. Except for the limited purpose of indicating that 416 | material is shared under a Creative Commons public license or as 417 | otherwise permitted by the Creative Commons policies published at 418 | creativecommons.org/policies, Creative Commons does not authorize the 419 | use of the trademark "Creative Commons" or any other trademark or logo 420 | of Creative Commons without its prior written consent including, 421 | without limitation, in connection with any unauthorized modifications 422 | to any of its public licenses or any other arrangements, 423 | understandings, or agreements concerning use of licensed material. For 424 | the avoidance of doubt, this paragraph does not form part of the 425 | public licenses. 426 | 427 | Creative Commons may be contacted at creativecommons.org. 428 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Rust Quiz 2 | ========= 3 | 4 | #### What is the output of this Rust program? 5 | 6 |
7 | 8 |

9 | 10 | 11 | 12 | https://dtolnay.github.io/rust-quiz 13 | 14 | 15 | 16 |

17 | 18 |

19 | 20 | 21 | 22 |

23 | 24 |
25 | 26 | *If you enjoy the Rust Quiz and also know C++, you may like to check out 27 | http://cppquiz.org which inspired this project.* 28 | 29 | ## Contributing 30 | 31 | I welcome suggestions for new quiz questions, either by filing a GitHub issue in 32 | this repository or by sending a pull request containing your question, hint, 33 | answer, and explanation of the answer. 34 | 35 | The best questions are drawn from personal experience writing or reading Rust 36 | code and being bewildered by its behavior. 37 | 38 | Aim for no more than 25 lines including blank lines, but shorter than that is 39 | better. Questions up to 35 lines may be accepted if there is no possible way to 40 | frame the same idea more concisely. Aim for no wider than 40 columns. The only 41 | exceptions on width will be for obvious boilerplate that is not necessary to 42 | read for solving the question. 43 | 44 | The website shows choices for "undefined behavior" and "does not compile", but 45 | please prefer adding questions that do compile and are well-defined. 46 | 47 | - Not fun: "does this program compile or does it not compile?" 48 | - Not fun: "is this undefined behavior or is it not?" 49 | - Fun: "does this print obvious possibility A or obvious possibility B?" 50 | 51 | To add a question, you need to add one *.rs* file and one *.md* file under the 52 | *questions/* directory. Pick a very brief (2-4 words) description to include in 53 | the file names. Please use 000 as the question number to avoid races between 54 | concurrent pull requests. I will assign a number when merging. 55 | 56 | Refer to an existing *.md* file and copy the format. In particular, you will 57 | need to provide a correct answer on the first line, a difficulty rating (1, 2 or 58 | 3) on the second line, a **Hint** section, and an **Explanation** section. 59 | 60 | The difficulty rating should primarily reflect how obscure is the knowledge 61 | required to confidently solve the question. 62 | 63 | When writing a hint, keep it brief. Maximum three lines, maximum two sentences 64 | is ideal. 65 | 66 | In the explanation, feel free to be as thorough as possible without dwelling on 67 | concepts that are not relevant to the crux of the quiz question. 68 | 69 | To launch the site locally and preview your rendered Markdown, run the 70 | following inside this directory. 71 | 72 | ```bash 73 | # Package all the questions into a single JavaScript file 74 | # and serve website over http at localhost:8000. 75 | cargo run -- serve 76 | ``` 77 | 78 | Then your question, assuming you numbered it 000, will be accessible at 79 | http\://localhost:8000/rust-quiz/0. 80 | 81 | ## License 82 | 83 | The quiz questions, explanations, website, and all other intellectual property 84 | in this repository are all licensed under the [Creative Commons 85 | Attribution-ShareAlike 4.0 International License](LICENSE-CC-BY-SA). 86 | -------------------------------------------------------------------------------- /docs/0/index.html: -------------------------------------------------------------------------------- 1 | ../index.html -------------------------------------------------------------------------------- /docs/1/index.html: -------------------------------------------------------------------------------- 1 | ../index.html -------------------------------------------------------------------------------- /docs/10/index.html: -------------------------------------------------------------------------------- 1 | ../index.html -------------------------------------------------------------------------------- /docs/11/index.html: -------------------------------------------------------------------------------- 1 | ../index.html -------------------------------------------------------------------------------- /docs/12/index.html: -------------------------------------------------------------------------------- 1 | ../index.html -------------------------------------------------------------------------------- /docs/13/index.html: -------------------------------------------------------------------------------- 1 | ../index.html -------------------------------------------------------------------------------- /docs/14/index.html: -------------------------------------------------------------------------------- 1 | ../index.html -------------------------------------------------------------------------------- /docs/15/index.html: -------------------------------------------------------------------------------- 1 | ../index.html -------------------------------------------------------------------------------- /docs/16/index.html: -------------------------------------------------------------------------------- 1 | ../index.html -------------------------------------------------------------------------------- /docs/17/index.html: -------------------------------------------------------------------------------- 1 | ../index.html -------------------------------------------------------------------------------- /docs/18/index.html: -------------------------------------------------------------------------------- 1 | ../index.html -------------------------------------------------------------------------------- /docs/19/index.html: -------------------------------------------------------------------------------- 1 | ../index.html -------------------------------------------------------------------------------- /docs/2/index.html: -------------------------------------------------------------------------------- 1 | ../index.html -------------------------------------------------------------------------------- /docs/20/index.html: -------------------------------------------------------------------------------- 1 | ../index.html -------------------------------------------------------------------------------- /docs/21/index.html: -------------------------------------------------------------------------------- 1 | ../index.html -------------------------------------------------------------------------------- /docs/22/index.html: -------------------------------------------------------------------------------- 1 | ../index.html -------------------------------------------------------------------------------- /docs/23/index.html: -------------------------------------------------------------------------------- 1 | ../index.html -------------------------------------------------------------------------------- /docs/24/index.html: -------------------------------------------------------------------------------- 1 | ../index.html -------------------------------------------------------------------------------- /docs/25/index.html: -------------------------------------------------------------------------------- 1 | ../index.html -------------------------------------------------------------------------------- /docs/26/index.html: -------------------------------------------------------------------------------- 1 | ../index.html -------------------------------------------------------------------------------- /docs/27/index.html: -------------------------------------------------------------------------------- 1 | ../index.html -------------------------------------------------------------------------------- /docs/28/index.html: -------------------------------------------------------------------------------- 1 | ../index.html -------------------------------------------------------------------------------- /docs/29/index.html: -------------------------------------------------------------------------------- 1 | ../index.html -------------------------------------------------------------------------------- /docs/3/index.html: -------------------------------------------------------------------------------- 1 | ../index.html -------------------------------------------------------------------------------- /docs/30/index.html: -------------------------------------------------------------------------------- 1 | ../index.html -------------------------------------------------------------------------------- /docs/31/index.html: -------------------------------------------------------------------------------- 1 | ../index.html -------------------------------------------------------------------------------- /docs/32/index.html: -------------------------------------------------------------------------------- 1 | ../index.html -------------------------------------------------------------------------------- /docs/33/index.html: -------------------------------------------------------------------------------- 1 | ../index.html -------------------------------------------------------------------------------- /docs/34/index.html: -------------------------------------------------------------------------------- 1 | ../index.html -------------------------------------------------------------------------------- /docs/35/index.html: -------------------------------------------------------------------------------- 1 | ../index.html -------------------------------------------------------------------------------- /docs/36/index.html: -------------------------------------------------------------------------------- 1 | ../index.html -------------------------------------------------------------------------------- /docs/37/index.html: -------------------------------------------------------------------------------- 1 | ../index.html -------------------------------------------------------------------------------- /docs/4/index.html: -------------------------------------------------------------------------------- 1 | ../index.html -------------------------------------------------------------------------------- /docs/5/index.html: -------------------------------------------------------------------------------- 1 | ../index.html -------------------------------------------------------------------------------- /docs/6/index.html: -------------------------------------------------------------------------------- 1 | ../index.html -------------------------------------------------------------------------------- /docs/7/index.html: -------------------------------------------------------------------------------- 1 | ../index.html -------------------------------------------------------------------------------- /docs/8/index.html: -------------------------------------------------------------------------------- 1 | ../index.html -------------------------------------------------------------------------------- /docs/9/index.html: -------------------------------------------------------------------------------- 1 | ../index.html -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Rust Quiz 8 | 9 | 10 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 |
34 |
35 |
36 |

Rust Quiz#1

37 |

What is the output of this Rust program?

38 |
39 | 40 | 43 | 44 |

 45 |           
 46 |         
47 | 48 |
49 |
50 |
51 | 52 | 53 |
54 |
55 | 56 | 57 |
58 |
59 | 60 | 63 | 64 | 65 | Playground 66 | 67 |
68 |
69 | 70 |
71 |
72 |

Correct!

73 | 74 |
75 |
76 | 77 |
78 | 79 |
80 |
81 | 82 | 95 | 96 |
97 |

Hint

98 | 99 |
100 |
101 |
102 | 103 | 113 |
114 | 115 |
116 | 117 |
118 |
119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | -------------------------------------------------------------------------------- /docs/quiz.css: -------------------------------------------------------------------------------- 1 | html, body { 2 | height: 100%; 3 | } 4 | 5 | .page { 6 | width: 100%; 7 | height: 100%; 8 | border-collapse: collapse; 9 | display: table; 10 | } 11 | 12 | .container { 13 | max-width: 650px; 14 | } 15 | 16 | .notbold { 17 | font-weight: 100; 18 | } 19 | 20 | .hljs { 21 | background: transparent; 22 | } 23 | 24 | #text-output { 25 | display: inline; 26 | margin-top: -100%; 27 | width: 100px; 28 | } 29 | 30 | .reset { 31 | display: table-row; 32 | vertical-align: bottom; 33 | text-align: right; 34 | height: 0; 35 | line-height: 0; 36 | } 37 | 38 | .reset-link { 39 | display: block; 40 | height: 0; 41 | position: relative; 42 | right: 10px; 43 | bottom: 10px; 44 | } 45 | 46 | .reset-link:after { 47 | content: "[Reset]"; 48 | } 49 | 50 | #accordion { 51 | transition: font-size .25s, opacity .5s .10s; 52 | padding-left: 10px; 53 | } 54 | 55 | @media screen and (min-width: 450px) { 56 | #accordion { 57 | padding-left: 30px !important; 58 | } 59 | } 60 | 61 | #accordion:not(.show) { 62 | font-size: 0; 63 | opacity: 0; 64 | transition: opacity .25s, font-size .5s .15s; 65 | } 66 | 67 | #button-reveal:not(:hover) #sad { 68 | display: none; 69 | } 70 | 71 | #explanation-content pre { 72 | padding-left: 30px; 73 | } 74 | 75 | p code { 76 | margin-left: 2px; 77 | margin-right: 2px; 78 | vertical-align: .5px; 79 | color: crimson; 80 | } 81 | 82 | #form label { 83 | display: inline !important; 84 | } 85 | 86 | a code { 87 | text-decoration: underline; 88 | } 89 | 90 | #footer p { 91 | margin-bottom: 0; 92 | } 93 | 94 | blockquote { 95 | padding-left: 30px; 96 | font-style: italic; 97 | } 98 | -------------------------------------------------------------------------------- /docs/quiz.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | // Current question. 4 | var q; 5 | var questionNumber = document.getElementById("question-number"); 6 | var current = document.getElementById("current"); 7 | 8 | // Code block. 9 | var accordion = document.getElementById("accordion"); 10 | var code = document.getElementById("code"); 11 | 12 | var form = document.getElementById("form"); 13 | var tombstone = document.getElementById("tombstone"); 14 | 15 | // Radio buttons. 16 | var radioUndefined = document.getElementById("radio-undefined"); 17 | var radioError = document.getElementById("radio-error"); 18 | var radioOutput = document.getElementById("radio-output"); 19 | 20 | // Input area where answer is typed in. 21 | var textOutput = document.getElementById("text-output"); 22 | 23 | // Alert containing explanation. 24 | var explanationAlert = document.getElementById("explanation-alert"); 25 | var explanationCorrect = document.getElementById("explanation-correct"); 26 | var explanationContent = document.getElementById("explanation-content"); 27 | 28 | // Navigation buttons. 29 | var nav = document.getElementById("nav"); 30 | var buttonNext = document.getElementById("button-next"); 31 | var buttonSubmit = document.getElementById("button-submit"); 32 | var buttonHint = document.getElementById("button-hint"); 33 | var buttonSkip = document.getElementById("button-skip"); 34 | var buttonReveal = document.getElementById("button-reveal"); 35 | var buttonPlayground = document.getElementById("button-playground"); 36 | 37 | // Alert containing hint. 38 | var hintAlert = document.getElementById("hint-alert"); 39 | var hintContent = document.getElementById("hint-content"); 40 | 41 | // Progress. 42 | var state = {}; 43 | var incorrect = document.getElementById("incorrect"); 44 | var answered = document.getElementById("answered"); 45 | var total = document.getElementById("total"); 46 | var buttonReset = document.getElementById("reset"); 47 | var contribute = document.getElementById("contribute"); 48 | 49 | function init() { 50 | window.onpopstate = function (event) { 51 | function activate() { 52 | if (event.state) { 53 | q = event.state.question; 54 | } else { 55 | initQuestion(); 56 | } 57 | activateQuestion(); 58 | } 59 | 60 | accordion.classList.remove("show"); 61 | window.setTimeout(activate, 400); 62 | }; 63 | form.addEventListener("submit", checkAnswer); 64 | buttonNext.onclick = nextQuestion; 65 | buttonSubmit.onclick = checkAnswer; 66 | buttonHint.onclick = doHint; 67 | buttonSkip.onclick = nextQuestion; 68 | buttonReveal.onclick = doReveal; 69 | textOutput.onclick = function () { 70 | radioOutput.checked = true; 71 | }; 72 | textOutput.oninput = function () { 73 | hide(incorrect); 74 | radioOutput.checked = true; 75 | }; 76 | radioUndefined.onchange = radioError.onchange = function () { 77 | hide(incorrect); 78 | }; 79 | radioOutput.onchange = function () { 80 | hide(incorrect); 81 | textOutput.select(); 82 | }; 83 | total.innerHTML = countQuestions(); 84 | loadState(); 85 | updateProgress(); 86 | initQuestion(); 87 | activateQuestion(); 88 | } 89 | 90 | function initQuestion() { 91 | var history = window.history.state; 92 | if (history && typeof history.question === "number") { 93 | q = history.question; 94 | return; 95 | } 96 | 97 | var path = window.location.pathname; 98 | var pathMatch = /^\/rust-quiz\/([0-9]+)\/?$/g.exec(path); 99 | if (pathMatch !== null) { 100 | var key = pathMatch[1]; 101 | var number = parseInt(key, 10); 102 | if (!isNaN(number) && key in questions) { 103 | q = number; 104 | } 105 | } 106 | 107 | if (typeof q === "undefined") { 108 | pickRandomQuestion(); 109 | } 110 | 111 | try { 112 | setTitle(); 113 | var newPath = "/rust-quiz/" + q; 114 | window.history.replaceState({ question: q }, document.title, newPath); 115 | } catch (e) {} 116 | } 117 | 118 | function activateQuestion() { 119 | current.innerHTML = q; 120 | setDifficultyStars(); 121 | show(questionNumber); 122 | code.innerHTML = ""; 123 | code.appendChild(document.createTextNode(questions[q].code.trim())); 124 | hljs.highlightBlock(code); 125 | hide(buttonPlayground); 126 | hide(explanationAlert); 127 | hide(incorrect); 128 | hide(hintAlert); 129 | buttonSkip.blur(); 130 | radioUndefined.checked = false; 131 | radioError.checked = false; 132 | radioOutput.checked = false; 133 | textOutput.value = ""; 134 | buttonHint.disabled = false; 135 | show(nav); 136 | if (questions[q].answer) { 137 | show(form); 138 | hide(tombstone); 139 | } else { 140 | hide(form); 141 | show(tombstone); 142 | } 143 | setTitle(); 144 | accordion.classList.add("show"); 145 | } 146 | 147 | function setDifficultyStars() { 148 | if (questions[q].difficulty) { 149 | var filled = questions[q].difficulty; 150 | var empty = 3 - filled; 151 | questionNumber.title = 152 | "Difficulty: " + "★".repeat(filled) + "☆".repeat(empty); 153 | } else { 154 | questionNumber.removeAttribute("title"); 155 | } 156 | } 157 | 158 | function nextQuestion() { 159 | function activate() { 160 | loadState(); 161 | pickRandomQuestion(); 162 | activateQuestion(); 163 | 164 | try { 165 | var path = "/rust-quiz/" + q; 166 | window.history.pushState({ question: q }, document.title, path); 167 | } catch (e) {} 168 | } 169 | 170 | accordion.classList.remove("show"); 171 | window.setTimeout(activate, 400); 172 | } 173 | 174 | function checkAnswer(e) { 175 | e.preventDefault(); 176 | 177 | var correct; 178 | if (radioUndefined.checked) { 179 | correct = questions[q].answer === "undefined"; 180 | } else if (radioError.checked) { 181 | correct = questions[q].answer === "error"; 182 | } else if (radioOutput.checked) { 183 | correct = questions[q].answer === textOutput.value.trim(); 184 | } else { 185 | buttonSubmit.blur(); 186 | return false; 187 | } 188 | 189 | if (correct) { 190 | show(explanationCorrect); 191 | recordCorrect(); 192 | showExplanation(); 193 | } else { 194 | show(incorrect); 195 | buttonSubmit.blur(); 196 | } 197 | 198 | return false; 199 | } 200 | 201 | function doHint() { 202 | hintContent.innerHTML = questions[q].hint.trim(); 203 | show(hintAlert); 204 | buttonHint.blur(); 205 | buttonHint.disabled = true; 206 | } 207 | 208 | function doReveal() { 209 | hide(explanationCorrect); 210 | showExplanation(); 211 | } 212 | 213 | function showExplanation() { 214 | hide(incorrect); 215 | hide(nav); 216 | hide(hintAlert); 217 | explanationContent.innerHTML = questions[q].explanation.trim(); 218 | if (questions[q].answer === "undefined") { 219 | radioUndefined.checked = true; 220 | } else if (questions[q].answer === "error") { 221 | radioError.checked = true; 222 | } else { 223 | radioOutput.checked = true; 224 | textOutput.value = questions[q].answer; 225 | } 226 | buttonPlayground.href = 227 | "https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&code=" + 228 | encodeURIComponent(questions[q].code.trim()); 229 | show(buttonPlayground); 230 | show(explanationAlert); 231 | textOutput.blur(); 232 | } 233 | 234 | function loadState() { 235 | if (storageAvailable()) { 236 | var json = window.localStorage.getItem("rust-quiz-answered"); 237 | if (json) { 238 | state = JSON.parse(json); 239 | } else { 240 | state = {}; 241 | } 242 | } 243 | } 244 | 245 | function saveState() { 246 | if (storageAvailable()) { 247 | var json = JSON.stringify(state); 248 | window.localStorage.setItem("rust-quiz-answered", json); 249 | } 250 | } 251 | 252 | function pickRandomQuestion() { 253 | var candidates = []; 254 | var unanswered = []; 255 | for (var i in questions) { 256 | var number = parseInt(i, 10); 257 | if (isNaN(number) || number === q || !questions[number].answer) { 258 | continue; 259 | } 260 | candidates.push(number); 261 | if (!state[number]) { 262 | unanswered.push(number); 263 | } 264 | } 265 | 266 | if (unanswered.length > 0) { 267 | candidates = unanswered; 268 | } 269 | 270 | var rand = Math.floor(Math.random() * candidates.length); 271 | q = candidates[rand]; 272 | } 273 | 274 | function setTitle() { 275 | document.title = "Rust Quiz #" + q; 276 | } 277 | 278 | function countQuestions() { 279 | var size = 0; 280 | for (var key in questions) { 281 | if (questions[key].answer) { 282 | size += 1; 283 | } 284 | } 285 | return size; 286 | } 287 | 288 | function updateProgress() { 289 | var count = 0; 290 | for (var key in questions) { 291 | if (questions[key].answer && state[key]) { 292 | count++; 293 | } 294 | } 295 | answered.innerHTML = count; 296 | if (count === countQuestions()) { 297 | show(contribute); 298 | } 299 | if (count > 0) { 300 | show(buttonReset); 301 | } 302 | } 303 | 304 | function recordCorrect() { 305 | loadState(); 306 | state[q] = true; 307 | saveState(); 308 | updateProgress(); 309 | } 310 | 311 | function reset() { 312 | state = {}; 313 | window.localStorage.clear(); 314 | answered.innerHTML = 0; 315 | hide(contribute); 316 | hide(buttonReset); 317 | } 318 | 319 | function show(element) { 320 | element.classList.remove("d-none"); 321 | } 322 | 323 | function hide(element) { 324 | element.classList.add("d-none"); 325 | } 326 | 327 | function storageAvailable() { 328 | try { 329 | var x = "__storage_test__"; 330 | window.localStorage.setItem(x, x); 331 | window.localStorage.removeItem(x); 332 | return true; 333 | } catch (e) { 334 | return false; 335 | } 336 | } 337 | 338 | init(); 339 | -------------------------------------------------------------------------------- /eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import globals from "globals"; 2 | import pluginJs from "@eslint/js"; 3 | 4 | /** @type {import('eslint').Linter.Config[]} */ 5 | export default [ 6 | { 7 | files: ["**/*.js"], 8 | languageOptions: { 9 | sourceType: "script", 10 | }, 11 | }, 12 | pluginJs.configs.recommended, 13 | { 14 | files: ["docs/questions.js"], 15 | rules: { 16 | "no-unused-vars": ["error", { varsIgnorePattern: "^questions$" }], 17 | }, 18 | }, 19 | { 20 | files: ["docs/quiz.js"], 21 | languageOptions: { 22 | globals: { 23 | hljs: "readonly", 24 | questions: "readonly", 25 | ...globals.browser, 26 | }, 27 | }, 28 | rules: { 29 | "no-empty": ["error", { allowEmptyCatch: true }], 30 | "no-unused-vars": [ 31 | "error", 32 | { caughtErrors: "none", varsIgnorePattern: "^reset$" }, 33 | ], 34 | }, 35 | }, 36 | ]; 37 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "devDependencies": { 3 | "@eslint/js": "^9.19.0", 4 | "eslint": "^9.19.0", 5 | "globals": "^15.14.0" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /questions/001-macro-count-statements.md: -------------------------------------------------------------------------------- 1 | Answer: 112 2 | Difficulty: 3 3 | 4 | # Hint 5 | 6 | The expression in the output of the macro evaluates to the same value as `1 << 7 | (n - 1)` where `n` is the number of statements contained in the macro input. 8 | 9 | # Explanation 10 | 11 | This question revolves around where the Rust grammar places statement 12 | boundaries. 13 | 14 | The input rule of the macro `m!` is `$($s:stmt)*` which matches zero or more 15 | Rust statements. The `$(`...`)*` part of the rule is a *repetition* which 16 | matches the contents of the repetition zero or more times, and the `$s:stmt` is 17 | a fragment specifier that matches a Rust statement (`stmt`) conforming to the 18 | rules of the Rust grammar. The matched statements are available within the 19 | expanded code as the fragment variable `$s`. 20 | 21 | A *statement* is the top-level unit of syntax permitted within a function body. 22 | All of the following are examples of statements. The grammar of function bodies 23 | requires that some types of statements are followed by a semicolon, but the 24 | semicolon is not part of the statement for the purpose of macro syntax. 25 | 26 | ```rust 27 | // Items are statements. 28 | struct S { x: u64 } 29 | 30 | // Let-bindings are statements. 31 | let mut s = S { x: 1 } 32 | 33 | // Expressions are statements. 34 | s.x + 1 35 | ``` 36 | 37 | The macro `m!` expands to zero or more copies of `{ stringify!($s); 1 }` 38 | separated by the `<<` token. The `$(`...`)<<*` part of the rule is a repetition 39 | using `<<` as the separator. 40 | 41 | Using `<<` as a separator in a repetition in a macro is highly unusual. The most 42 | commmonly used separator is the comma, written as `$(`...`),*`, but any other 43 | single token is allowed here. Crucially, `macro_rules!` treats all built-in Rust 44 | operators as single tokens, even those that consist of multiple characters like 45 | `<<`. 46 | 47 | The `{ stringify!($s); 1 }` is an expression whose value is always 1. The value 48 | of `stringify!($s)` is discarded, so this is equivalent to the expression `{ 1 49 | }`. The reason for having `stringify!($s)` in there is to control the number of 50 | times the repetition is repeated, which is determined by which fragment 51 | variables are used within the repetition. Writing a repetition without using any 52 | fragment variables inside of it would not be legal. 53 | 54 | Suppose we call this macro with three of the statements shown above as input. 55 | 56 | ```rust 57 | m! { 58 | struct S { x: u64 } 59 | let mut s = S { x: 1 } 60 | s.x + 1 61 | } 62 | ``` 63 | 64 | The macro expands to: 65 | 66 | ```rust 67 | { stringify!(struct S { x: u64 }); 1 } 68 | << { stringify!(let mut s = S { x: 1 }); 1 } 69 | << { stringify!(s.x + 1); 1 } 70 | ``` 71 | 72 | Each of the `stringify`s expands to a string literal: 73 | 74 | ```rust 75 | { "struct S { x: u64 }"; 1 } 76 | << { "let mut s = S { x: 1 }"; 1 } 77 | << { "s.x + 1"; 1 } 78 | ``` 79 | 80 | The values of the string literals are not used. In this case the expression is 81 | equivalent to `{ 1 } << { 1 } << { 1 }`, which is equivalent to `1 << 1 << 1`. 82 | The `<<` operator is left-associative; the numeric value of this expression is 83 | 4\. 84 | 85 | Altogether, the relevant behavior of this macro is that it evaluates to `1 << 1 86 | << 1 << ...` where the number of ones is equal to the number of Rust statements 87 | in the input of the macro. In closed form, the numeric value is `1 << (n - 1)` 88 | where `n` is the number of statements, except in the case that `n` is zero where 89 | the macro expands to nothing and we get a syntax error at the call site. 90 | 91 | It remains to determine how many statements are in the three invocations of 92 | `m!` in the quiz code. 93 | 94 | 1. `return || true` 95 | 96 | This is a return-expression that would return the closure `|| true`. It is 97 | equivalent to `return (|| true)`. It is parsed as a single statement so the 98 | `m!` invocation evaluates to `1`. 99 | 100 | 2. `(return) || true` 101 | 102 | This is a logical-OR expression. The `||` is a binary operator, where the 103 | left-hand side is the expression `(return)` (of diverging type `!`) and the 104 | right-hand side is the expression `true`. This expression is a single 105 | statement so `m!` again evaluates to `1`. 106 | 107 | 3. `{return} || true` 108 | 109 | This one is two statements! A block-statement `{return}` followed by a 110 | closure expression `|| true`. 111 | 112 | The Rust grammar distinguishes between expressions that require a semicolon 113 | in order to stand alone as a statement, and expressions that can be 114 | statements even without a semicolon. Consider two examples: 115 | 116 | ```rust 117 | // No trailing semicolon required. 118 | for t in vec { 119 | /* ... */ 120 | } 121 | 122 | // Trailing semicolon required. 123 | self.skip_whitespace()?; 124 | ``` 125 | 126 | The list of expression types that stand alone without a semicolon is defined 127 | [here][classify] in libsyntax. The distinction informs a few different early 128 | bail-out cases where the parser decides to finish parsing the current 129 | expression. 130 | 131 | Relevant to our case is that block expressions `{ /* ... */ }` terminate an 132 | expression if doing so would be syntactically sensible. The parser does not 133 | eagerly consume binary operators after a block expression. Thus one might 134 | write: 135 | 136 | ```rust 137 | fn f() -> &'static &'static bool { 138 | // Block expression. 139 | { 140 | println!("What a silly function."); 141 | } 142 | 143 | // Reference to reference to true. 144 | &&true 145 | } 146 | ``` 147 | 148 | In order to parse a block followed by a binary operator, we would need to 149 | make it syntactically insensible for the parser to terminate an expression 150 | at the close curly brace. This would usually be done by wrapping in 151 | parentheses. 152 | 153 | ```rust 154 | fn f() -> bool { 155 | ({ true } && true) 156 | } 157 | ``` 158 | 159 | [classify]: https://github.com/rust-lang/rust/blob/1.30.1/src/libsyntax/parse/classify.rs#L17-L37 160 | 161 | Anyhow, the output of the program is `112`. 162 | -------------------------------------------------------------------------------- /questions/001-macro-count-statements.rs: -------------------------------------------------------------------------------- 1 | macro_rules! m { 2 | ($($s:stmt)*) => { 3 | $( 4 | { stringify!($s); 1 } 5 | )<<* 6 | }; 7 | } 8 | 9 | fn main() { 10 | print!( 11 | "{}{}{}", 12 | m! { return || true }, 13 | m! { (return) || true }, 14 | m! { {return} || true }, 15 | ); 16 | } 17 | -------------------------------------------------------------------------------- /questions/002-bitand-or-reference.md: -------------------------------------------------------------------------------- 1 | Answer: 123 2 | Difficulty: 2 3 | 4 | # Hint 5 | 6 | One of these four closures is unlike the other three. 7 | 8 | # Explanation 9 | 10 | The closures `f`, `g`, and `h` are all of type `impl Fn()`. The closure bodies 11 | are parsed as an invocation of the user-defined bitwise-AND operator defined 12 | above by the `BitAnd` trait impl. When the closures are invoked, the bitwise-AND 13 | implementation prints the content of the `S` from the right-hand side and 14 | evaluates to `()`. 15 | 16 | The closure `i` is different. Formatting the code with rustfmt makes it clearer 17 | how `i` is parsed. 18 | 19 | ```rust 20 | let i = || { 21 | {} 22 | &S(4) 23 | }; 24 | ``` 25 | 26 | The closure body consists of an empty block-statement `{}` followed by a 27 | *reference* to `S(4)`, not a bitwise-AND. The type of `i` is `impl Fn() -> 28 | &'static S`. 29 | 30 | The parsing of this case is governed by [this code][classify] in libsyntax. 31 | 32 | [classify]: https://github.com/rust-lang/rust/blob/1.30.1/src/libsyntax/parse/classify.rs#L17-L37 33 | -------------------------------------------------------------------------------- /questions/002-bitand-or-reference.rs: -------------------------------------------------------------------------------- 1 | struct S(i32); 2 | 3 | impl std::ops::BitAnd for () { 4 | type Output = (); 5 | 6 | fn bitand(self, rhs: S) { 7 | print!("{}", rhs.0); 8 | } 9 | } 10 | 11 | fn main() { 12 | let f = || ( () & S(1) ); 13 | let g = || { () & S(2) }; 14 | let h = || ( {} & S(3) ); 15 | let i = || { {} & S(4) }; 16 | f(); 17 | g(); 18 | h(); 19 | i(); 20 | } 21 | -------------------------------------------------------------------------------- /questions/003-mutate-const.md: -------------------------------------------------------------------------------- 1 | Answer: 32 2 | Difficulty: 1 3 | Warnings: const_item_mutation 4 | 5 | # Hint 6 | 7 | In what ways is a `const` different from a non-mut `static`? 8 | 9 | # Explanation 10 | 11 | The semantics of `const` is that any mention of the `const` by name in 12 | expression position is substituted with the value of the `const` initializer. In 13 | this quiz code the behavior is equivalent to: 14 | 15 | ```rust 16 | struct S { 17 | x: i32, 18 | } 19 | 20 | fn main() { 21 | let v = &mut S { x: 2 }; 22 | v.x += 1; 23 | S { x: 2 }.x += 1; 24 | print!("{}{}", v.x, S { x: 2 }.x); 25 | } 26 | ``` 27 | 28 | I have simply substituted every mention of `S` in expresson position with the 29 | value of `const S` which is `S { x: 2 }`. 30 | 31 | The first line of `main` is equivalent to: 32 | 33 | ```rust 34 | let mut _tmp0 = S { x: 2 }; 35 | let v = &mut _tmp0; 36 | ``` 37 | 38 | The second line of `main` mutates the value pointed to by `v`. The same value 39 | remains accessible through `v` for the rest of the lifetime of `v`, which is why 40 | the first character printed is `3`. 41 | 42 | The third line of `main` mutates a temporary that immediately goes out of scope 43 | at the semicolon. The second character printed is coming from a brand new `S { 44 | x: 2 }`, so `2` is printed. 45 | 46 | One additional wrinkle in this code is the concept of namespaces and name 47 | resolution in Rust. Any name that refers to a *type* lives in the *type 48 | namespace*, and any name that refers to a *value* lives in the *value 49 | namespace*. These are two separate sets of names, and the language is structured 50 | such that we can always tell which namespace to look up a name in. 51 | 52 | In the context of the quiz code, the name of the struct `S` is part of the type 53 | namespace and the name of the const `S` is part of the value namespace. That is 54 | how we can have seemingly two different things with the same name in scope at 55 | the same time. 56 | -------------------------------------------------------------------------------- /questions/003-mutate-const.rs: -------------------------------------------------------------------------------- 1 | struct S { 2 | x: i32, 3 | } 4 | 5 | const S: S = S { x: 2 }; 6 | 7 | fn main() { 8 | let v = &mut S; 9 | v.x += 1; 10 | S.x += 1; 11 | print!("{}{}", v.x, S.x); 12 | } 13 | -------------------------------------------------------------------------------- /questions/004-dotdot-in-tuple.md: -------------------------------------------------------------------------------- 1 | Answer: 54 2 | Difficulty: 1 3 | 4 | # Hint 5 | 6 | `..` means one thing in an expression and something else in a pattern. 7 | 8 | # Explanation 9 | 10 | This question demonstrates two different meanings of `..`. 11 | 12 | In expression position, `..` is the syntax for constructing various types of 13 | ranges. Here the expression `(0, 1, ..)` is a tuple with three elements, the 14 | third one having type [`RangeFull`]. 15 | 16 | [`RangeFull`]: https://doc.rust-lang.org/std/ops/struct.RangeFull.html 17 | 18 | On the other hand in a pattern, `..` is used to mean "any number of elements". 19 | So the pattern `(.., x, y)` matches a tuple with 2 or more elements, binding the 20 | second-last one to `x` and the last one to `y`. 21 | 22 | Coming out of the first line of `main`, we have `x = 1` and `y = (..)`. Thus the 23 | value printed is going to be `b"066"[..][1]`. 24 | 25 | The expression `b"066"` is a byte-string literal of type `&'static [u8; 3]` 26 | containing the three ASCII bytes `b'0'`, `b'6'`, `b'6'`. 27 | 28 | When we slice the byte-string with `RangeFull` we get a dynamically sized slice 29 | `[u8]` of length 3. Next we access element `1` of the slice, which is the byte 30 | `b'6'` of type `u8`. When printed, we see the decimal representation of the byte 31 | value of the ASCII digit 6, which is the number 54. 32 | -------------------------------------------------------------------------------- /questions/004-dotdot-in-tuple.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | let (.., x, y) = (0, 1, ..); 3 | print!("{}", b"066"[y][x]); 4 | } 5 | -------------------------------------------------------------------------------- /questions/005-trait-resolution-hrtb.md: -------------------------------------------------------------------------------- 1 | tombstone 2 | -------------------------------------------------------------------------------- /questions/005-trait-resolution-hrtb.rs: -------------------------------------------------------------------------------- 1 | trait Trait { 2 | fn p(self); 3 | } 4 | 5 | impl Trait for fn(T) { 6 | fn p(self) { 7 | print!("1"); 8 | } 9 | } 10 | 11 | impl Trait for fn(&T) { 12 | fn p(self) { 13 | print!("2"); 14 | } 15 | } 16 | 17 | fn f(_: u8) {} 18 | fn g(_: &u8) {} 19 | 20 | fn main() { 21 | let a: fn(_) = f; 22 | let b: fn(_) = g; 23 | let c: fn(&_) = g; 24 | a.p(); 25 | b.p(); 26 | c.p(); 27 | } 28 | -------------------------------------------------------------------------------- /questions/006-value-of-assignment.md: -------------------------------------------------------------------------------- 1 | Answer: 0 2 | Difficulty: 1 3 | Warnings: unused_assignments, unused_variables 4 | 5 | # Hint 6 | 7 | There are two variables named `a`. What is the type of each one? 8 | 9 | # Explanation 10 | 11 | There are two variables named `a`, one shadowing the other. The program is 12 | equivalent to: 13 | 14 | ```rust 15 | let a; 16 | let b = a = true; 17 | print!("{}", mem::size_of_val(&b)); 18 | ``` 19 | 20 | Further, the value being assigned to `b` is the expression `a = true`. 21 | 22 | In Rust, assignment expressions always have the value `()`. Simplified some 23 | more, the quiz code is equivalent to: 24 | 25 | ```rust 26 | let a = true; 27 | let b = (); 28 | print!("{}", mem::size_of_val(&b)); 29 | ``` 30 | 31 | Refer to the documentation of [`size_of_val`] for a specification of its 32 | behavior, but in this case it is being instantiated with `T = ()` and we end up 33 | printing the value of `size_of::<()>()`. 34 | 35 | [`size_of_val`]: https://doc.rust-lang.org/std/mem/fn.size_of_val.html 36 | 37 | `()` is one example of a [*zero-sized type*][zst] or ZST and is represented by 38 | zero bytes of data at runtime, so the program prints `0`. 39 | 40 | [zst]: https://doc.rust-lang.org/nomicon/exotic-sizes.html#zero-sized-types-zsts 41 | -------------------------------------------------------------------------------- /questions/006-value-of-assignment.rs: -------------------------------------------------------------------------------- 1 | use std::mem; 2 | 3 | fn main() { 4 | let a; 5 | let a = a = true; 6 | print!("{}", mem::size_of_val(&a)); 7 | } 8 | -------------------------------------------------------------------------------- /questions/007-surprise-wildcard-match.md: -------------------------------------------------------------------------------- 1 | tombstone 2 | -------------------------------------------------------------------------------- /questions/007-surprise-wildcard-match.rs: -------------------------------------------------------------------------------- 1 | #[repr(u8)] 2 | enum Enum { 3 | First, 4 | Second, 5 | } 6 | 7 | impl Enum { 8 | fn p(self) { 9 | match self { 10 | First => print!("1"), 11 | Second => print!("2"), 12 | } 13 | } 14 | } 15 | 16 | fn main() { 17 | Enum::p(unsafe { 18 | std::mem::transmute(1u8) 19 | }); 20 | } 21 | -------------------------------------------------------------------------------- /questions/008-tokenize-punctuation.md: -------------------------------------------------------------------------------- 1 | Answer: 1214 2 | Difficulty: 2 3 | 4 | # Hint 5 | 6 | According to `macro_rules!`, `==` is one token and `=>` is one token. 7 | 8 | # Explanation 9 | 10 | Adjacent punctuation characters in the input pattern of a `macro_rules!` macro 11 | are grouped according to how those characters are used by native Rust tokens. 12 | 13 | [This page][tokens] contains a list of the single-character and multi-character 14 | punctuation tokens involved in the Rust grammar. 15 | 16 | [tokens]: https://docs.rs/syn/0.15.22/syn/token/index.html#structs 17 | 18 | As one example from that list, `<<=` is a single token because the Rust grammar 19 | uses that sequence of characters to mean [left shift assignment][ShlAssign]. 20 | Thus a `macro_rules!` input rule containing `<<=` would only match if all three 21 | characters `<<=` are written consecutively without spaces in the invocation. 22 | 23 | [ShlAssign]: https://doc.rust-lang.org/std/ops/trait.ShlAssign.html 24 | 25 | But for example `=<<` is not a native token in the Rust grammar. The parser of 26 | `macro_rules!` will decompose this into Rust tokens according to a greedy 27 | process. `=<` is also not a native token, so first we would need to match a `=` 28 | by itself. Then `<<` *is* a native token. Writing `=<<` in a macro rule behaves 29 | exactly the same as writing `= <<`. 30 | 31 | Now let's decompose the rules in the quiz code the same way. 32 | 33 | - `==>` decomposes as `== >`. 34 | - `= = >` is already decomposed into Rust tokens. 35 | - `== >` is already decomposed. 36 | - `= =>` is already decomposed. 37 | 38 | Our macro is the same as if we had written the first rule with a space. The 39 | third rule is unreachable. 40 | 41 | ```rust 42 | macro_rules! m { 43 | (== >) => { print!("1"); }; 44 | (= = >) => { print!("2"); }; 45 | (== >) => { print!("3"); }; 46 | (= =>) => { print!("4"); }; 47 | } 48 | ``` 49 | 50 | Within `main`, the first and third lines *both* match the first macro rule. The 51 | second line matches the second rule and the fourth line matches the fourth rule. 52 | The output is `1214`. 53 | 54 | [Procedural macros][syn] use a more flexible and powerful macro API and can 55 | always distinguish between different spacings of the same characters, such as 56 | `== >` vs `==>`. 57 | 58 | [syn]: https://github.com/dtolnay/syn 59 | -------------------------------------------------------------------------------- /questions/008-tokenize-punctuation.rs: -------------------------------------------------------------------------------- 1 | macro_rules! m { 2 | (==>) => { print!("1"); }; 3 | (= = >) => { print!("2"); }; 4 | (== >) => { print!("3"); }; 5 | (= =>) => { print!("4"); }; 6 | } 7 | 8 | fn main() { 9 | m!(==>); 10 | m!(= = >); 11 | m!(== >); 12 | m!(= =>); 13 | } 14 | -------------------------------------------------------------------------------- /questions/009-opaque-metavariable.md: -------------------------------------------------------------------------------- 1 | Answer: 21 2 | Difficulty: 2 3 | 4 | # Hint 5 | 6 | Upon being matched as a `$:expr`, the matched expression becomes a single opaque 7 | token tree. 8 | 9 | # Explanation 10 | 11 | This question involves the behavior of macro matchers as regards matching macro 12 | metavariables. 13 | 14 | Starting from the bottom of the quiz code, the invocation `t!(1)` matches the 15 | first rule of `t!` and expands to `e!(1); m!(1);`. 16 | 17 | The invocation `e!(1)` matches the first rule of `e!`. As part of this match, 18 | the expression `1` is packaged into an opaque expression token called `$e`. At 19 | no subsequent point will it be possible for any `macro_rules!` macro to look 20 | inside of `$e`. All that can be known is that `$e` is *some* expression. 21 | 22 | In any case, `e!(1)` expands to `m!($e)` where `$e` is an opaque expression 23 | containing `1`. That `m!($e)` *does not* match the first rule of `m!` because 24 | `$e` is opaque. Instead it matches the second rule of `m!` and prints `2`. 25 | 26 | After `e!(1)` there is an invocation `m!(1)` coming from the expansion of `t!`. 27 | That one *does* match the first rule of `m!` and prints `1`. The output of this 28 | program is `21`. 29 | 30 | Most fragment specifiers have this behavior of becoming opaque token boxes, but 31 | some do not. Specifiers that are opaque once matched: 32 | 33 | - `$:block` 34 | - `$:expr` 35 | - `$:item` 36 | - `$:literal` 37 | - `$:meta` 38 | - `$:pat` 39 | - `$:path` 40 | - `$:stmt` 41 | - `$:ty` 42 | 43 | The rest of the specifiers do not become opaque and can be inspected by 44 | subsequent rules: 45 | 46 | - `$:ident` 47 | - `$:lifetime` 48 | - `$:tt` 49 | 50 | For example: 51 | 52 | ```rust 53 | macro_rules! m { 54 | ('a) => {}; 55 | } 56 | 57 | macro_rules! l { 58 | ($l:lifetime) => { 59 | // $l is not opaque. 60 | m!($l); 61 | } 62 | } 63 | 64 | l!('a); 65 | ``` 66 | -------------------------------------------------------------------------------- /questions/009-opaque-metavariable.rs: -------------------------------------------------------------------------------- 1 | macro_rules! m { 2 | (1) => { print!("1") }; 3 | ($tt:tt) => { print!("2") }; 4 | } 5 | 6 | macro_rules! e { 7 | ($e:expr) => { m!($e) }; 8 | } 9 | 10 | macro_rules! t { 11 | ($tt:tt) => { e!($tt); m!($tt); }; 12 | } 13 | 14 | fn main() { 15 | t!(1); 16 | } 17 | -------------------------------------------------------------------------------- /questions/010-shadowed-trait-object-method.md: -------------------------------------------------------------------------------- 1 | Answer: 222222 2 | Difficulty: 2 3 | Warnings: dead_code 4 | 5 | # Hint 6 | 7 | This won't help you answer the question but may help feel better: the quiz 8 | author was also stumped by this one. 9 | 10 | # Explanation 11 | 12 | This question contains a trait method `Trait::f` as well as an inherent method 13 | `f` on the trait object type `dyn Trait`. 14 | 15 | *As far as I know,* given that these names shadow each other, the inherent 16 | method is literally uncallable. There is currently no syntax in Rust for calling 17 | the inherent `f` on `dyn Trait`. 18 | 19 | One additional syntax to try would be: 20 | 21 | ```rust 22 | ::f(&true); 23 | ::f(&true as &dyn Trait); 24 | ``` 25 | 26 | If the trait method were named something different, both of these would call the 27 | inherent method. If the inherent method were named something different, both of 28 | these would call the trait method. But if the trait method and the inherent 29 | method are both `f` then the compiler reports an ambiguity. 30 | 31 | ``` 32 | error[E0034]: multiple applicable items in scope 33 | --> questions/010.rs:18:5 34 | | 35 | 18 | ::f(&true); 36 | | ^^^^^^^^^^^^^^ multiple `f` found 37 | | 38 | note: candidate #1 is defined in an impl for the type `dyn Trait` 39 | --> questions/010.rs:6:5 40 | | 41 | 6 | fn f(&self) { 42 | | ^^^^^^^^^^^ 43 | note: candidate #2 is defined in the trait `Trait` 44 | --> questions/010.rs:2:5 45 | | 46 | 2 | fn f(&self); 47 | | ^^^^^^^^^^^^ 48 | = help: to disambiguate the method call, write `Trait::f(...)` instead 49 | ``` 50 | 51 | Maybe some day it will be possible to disambiguate a call to an inherent method 52 | on a trait object shadowed by a trait method. For now, the quiz code prints 53 | `222222`. 54 | -------------------------------------------------------------------------------- /questions/010-shadowed-trait-object-method.rs: -------------------------------------------------------------------------------- 1 | trait Trait { 2 | fn f(&self); 3 | } 4 | 5 | impl<'a> dyn Trait + 'a { 6 | fn f(&self) { 7 | print!("1"); 8 | } 9 | } 10 | 11 | impl Trait for bool { 12 | fn f(&self) { 13 | print!("2"); 14 | } 15 | } 16 | 17 | fn main() { 18 | Trait::f(&true); 19 | Trait::f(&true as &dyn Trait); 20 | <_ as Trait>::f(&true); 21 | <_ as Trait>::f(&true as &dyn Trait); 22 | ::f(&true); 23 | ::f(&true as &dyn Trait); 24 | } 25 | -------------------------------------------------------------------------------- /questions/011-function-pointer-comparison.md: -------------------------------------------------------------------------------- 1 | Answer: error 2 | Difficulty: 3 3 | 4 | # Hint 5 | 6 | The way that `f` and `g` are written is not interchangeable. 7 | 8 | # Explanation 9 | 10 | Function pointer comparison is generally a Bad Idea. It is easily possible to 11 | get nonsensical behavior in optimized builds. For a jaw-dropping example of such 12 | behavior, check out [rust-lang/rust#54685] in which `x == y` is both true and 13 | not true at the same time. 14 | 15 | [rust-lang/rust#54685]: https://github.com/rust-lang/rust/issues/54685 16 | 17 | That said, the quiz code in this question fails to compile. Here is the compiler 18 | output: 19 | 20 | ``` 21 | error: cannot specify lifetime arguments explicitly if late bound lifetime parameters are present 22 | --> questions/011.rs:5:18 23 | | 24 | 5 | let pf = f::<'static> as fn(); 25 | | ^^^^^^^ 26 | | 27 | note: the late bound lifetime parameter is introduced here 28 | --> questions/011.rs:1:18 29 | | 30 | 1 | fn f<'a>() {} 31 | | ^^ 32 | ``` 33 | 34 | Generic parameters can be either early bound or late bound. Currently (and for 35 | the foreseeable future) type parameters are always early bound, but lifetime 36 | parameters can be either early or late bound. 37 | 38 | Early bound parameters are determined by the compiler during monomorphization. 39 | Since type parameters are always early bound, you cannot have a value whose 40 | type has an unresolved type parameter. For example: 41 | 42 | ``` 43 | fn m() {} 44 | 45 | fn main() { 46 | let m1 = m::; // ok 47 | let m2 = m; // error: cannot infer type for `T` 48 | } 49 | ``` 50 | 51 | However, this is often allowed for lifetime parameters: 52 | 53 | ``` 54 | fn m<'a>(_: &'a ()) {} 55 | 56 | fn main() { 57 | let m1 = m; // ok even though 'a isn't provided 58 | } 59 | ``` 60 | 61 | Since the actual choice of lifetime `'a` depends on how it is called, we are 62 | allowed to omit the lifetime parameter and it will be determined at the call 63 | site. The lifetime can even be different for each time it gets called. 64 | 65 | For this reason, we cannot specify the lifetime on this function until it is 66 | called: 67 | 68 | ``` 69 | // error: cannot specify lifetime arguments explicitly if late bound lifetime parameters are present 70 | let m2 = m::<'static>; 71 | ``` 72 | 73 | We may not even ask the borrow checker to infer it too soon: 74 | 75 | ``` 76 | // error: cannot specify lifetime arguments explicitly if late bound lifetime parameters are present 77 | let m3 = m::<'_>; 78 | ``` 79 | 80 | The idea of late bound parameters overlaps considerably with a feature of Rust 81 | called "higher ranked trait bounds" (HRTB). This is a mechanism for expressing 82 | that bounds on a trait's parameters are late bound. Currently this is limited to 83 | lifetime parameters, but the same idea exists in other languages (such as 84 | Haskell) for type parameters, which is where the term "higher ranked" comes 85 | from. 86 | 87 | The syntax to express a HRTB for lifetimes uses the `for` keyword. To express 88 | the type of `m1` above, we could have written: 89 | 90 | ``` 91 | let m1: impl for<'r> Fn(&'r ()) = m; 92 | ``` 93 | 94 | You can think of this as meaning: "There is a lifetime but we don't need to 95 | know what it is just yet". 96 | 97 | Late bound lifetimes are always unbounded; there is no syntax for expressing a 98 | late bound lifetime that must outlive some other lifetime. 99 | 100 | ``` 101 | error: lifetime bounds cannot be used in this context 102 | --> src/main.rs:5:20 103 | | 104 | 5 | let _: for<'b: 'a> fn(&'b ()); 105 | | ^^ 106 | ``` 107 | 108 | Lifetimes on _data types_ are always early bound except when the developer has 109 | explicitly used the HRTB `for` syntax. On _functions_, lifetimes are late bound 110 | by default but can be early bound if: 111 | 112 | * The lifetime is declared outside the function signature, e.g. in an associated 113 | method of a struct it could be from the struct itself; or 114 | 115 | * The lifetime parameter is bounded below by some other lifetime that it must 116 | outlive. As we've seen, this constraint is not expressible in the HRTB that 117 | would be involved in late binding the lifetime. 118 | 119 | By these rules, the signature `fn f<'a>()` has a late bound lifetime parameter 120 | while the signature `fn g<'a: 'a>()` has an early bound lifetime parameter — 121 | even though the constraint here is ineffectual. 122 | 123 | Ordinarily these distinctions are compiler-internal terminology that Rust 124 | programmers are not intended to know about or think about in everyday code. 125 | There are only a few edge cases where this aspect of the type system becomes 126 | observable in the surface language, such as in the original quiz code. 127 | -------------------------------------------------------------------------------- /questions/011-function-pointer-comparison.rs: -------------------------------------------------------------------------------- 1 | fn f<'a>() {} 2 | fn g<'a: 'a>() {} 3 | 4 | fn main() { 5 | let pf = f::<'static> as fn(); 6 | let pg = g::<'static> as fn(); 7 | print!("{}", (pf == pg) as u8); 8 | } 9 | -------------------------------------------------------------------------------- /questions/012-binding-drop-behavior.md: -------------------------------------------------------------------------------- 1 | Answer: 1243 2 | Difficulty: 1 3 | Warnings: dead_code 4 | 5 | # Hint 6 | 7 | The pattern `S { ref x, .. }` borrows a binding `x` from the owner of a value of 8 | type `S`. 9 | 10 | # Explanation 11 | 12 | This question involves drop-placement. Where does `D` get dropped? 13 | 14 | In the first `let`-binding, we [destructure] a value of type `S` into its field 15 | `x` of type `u8` as well as `..` which represents "the rest of `S`". The part 16 | that is the rest of `S` is dropped immediately at that point because it no 17 | longer has an owner. 18 | 19 | [destructure]: https://doc.rust-lang.org/book/ch18-03-pattern-syntax.html#destructuring-to-break-apart-values 20 | 21 | In the second `let`-binding, we borrow a field `x` from the owner of a value of 22 | type `S`. The whole value of type `S` remains in scope during the time that its 23 | field `x` is borrowed, and goes out of scope at the close curly brace of `main`. 24 | 25 | The output is `1243`. 26 | -------------------------------------------------------------------------------- /questions/012-binding-drop-behavior.rs: -------------------------------------------------------------------------------- 1 | struct D(u8); 2 | 3 | impl Drop for D { 4 | fn drop(&mut self) { 5 | print!("{}", self.0); 6 | } 7 | } 8 | 9 | struct S { 10 | d: D, 11 | x: u8, 12 | } 13 | 14 | fn main() { 15 | let S { x, .. } = S { 16 | d: D(1), 17 | x: 2, 18 | }; 19 | print!("{}", x); 20 | 21 | let S { ref x, .. } = S { 22 | d: D(3), 23 | x: 4, 24 | }; 25 | print!("{}", x); 26 | } 27 | -------------------------------------------------------------------------------- /questions/013-mutable-zst.md: -------------------------------------------------------------------------------- 1 | Answer: 1 2 | Difficulty: 1 3 | 4 | # Hint 5 | 6 | Is it okay for two mutable references to point to the same memory location? What 7 | could go wrong? 8 | 9 | # Explanation 10 | 11 | In this code, `S` is a [zero sized type][zst] or ZST. Zero sized types are 12 | compile-time concepts that disappear during compilation and have a runtime 13 | representation of zero bytes. 14 | 15 | [zst]: https://doc.rust-lang.org/nomicon/exotic-sizes.html#zero-sized-types-zsts 16 | 17 | The first line of `main` creates a local value of type `[S; 2]`. Let's refer to 18 | that temporary as `tmp`. The `let`-binding binds two references into `tmp`, `x` 19 | referring to `&mut tmp[0]` and `y` referring to `&mut tmp[1]`. 20 | 21 | On the second line of `main` we want to know whether `x` and `y` as pointers 22 | have the same value. 23 | 24 | The array type `[S; 2]` is itself a zero sized type. You can confirm this by 25 | printing the value of `std::mem::size_of::<[S; 2]>()`. Indeed the first and 26 | second element of the array have the same memory address. 27 | 28 | Ordinarily having multiple mutable references to the same memory location would 29 | not be safe, but in the case of mutable references to zero sized types, 30 | dereferencing is a no-op so there is no way to violate any memory safety 31 | guarantees this way. 32 | -------------------------------------------------------------------------------- /questions/013-mutable-zst.rs: -------------------------------------------------------------------------------- 1 | struct S; 2 | 3 | fn main() { 4 | let [x, y] = &mut [S, S]; 5 | let eq = x as *mut S == y as *mut S; 6 | print!("{}", eq as u8); 7 | } 8 | -------------------------------------------------------------------------------- /questions/014-trait-autoref.md: -------------------------------------------------------------------------------- 1 | Answer: 10 2 | Difficulty: 1 3 | Warnings: non_local_definitions 4 | 5 | # Hint 6 | 7 | Trait method auto-ref is covered in [this Stack Overflow answer][SO]. 8 | 9 | [SO]: https://stackoverflow.com/a/28552082/6086311 10 | 11 | # Explanation 12 | 13 | Trait impls anywhere in a program are always in scope, so there is no 14 | significance to the `impl Trait for char` being written inside of a block of 15 | code. In particular, that impl is visible throughout the whole program, not just 16 | within the block containing the impl. 17 | 18 | This question relates to the behavior of trait method auto-ref which is covered 19 | in [this Stack Overflow answer][SO]. 20 | 21 | [SO]: https://stackoverflow.com/a/28552082/6086311 22 | 23 | The call to `0.is_reference()` observes that there is no implementation of 24 | `Trait` for an integer type that we could call directly. Method resolution 25 | inserts an auto-ref, effectively evaluating `(&0).is_reference()`. This time the 26 | call matches `impl<'a, T> Trait for &'a T` and prints `1`. 27 | 28 | The call to `'?'.is_reference()` instead finds `impl Trait for char`, printing 29 | `0`. 30 | -------------------------------------------------------------------------------- /questions/014-trait-autoref.rs: -------------------------------------------------------------------------------- 1 | trait Trait: Sized { 2 | fn is_reference(self) -> bool; 3 | } 4 | 5 | impl<'a, T> Trait for &'a T { 6 | fn is_reference(self) -> bool { 7 | true 8 | } 9 | } 10 | 11 | fn main() { 12 | match 0.is_reference() { 13 | true => print!("1"), 14 | false => print!("0"), 15 | } 16 | 17 | match '?'.is_reference() { 18 | true => print!("1"), 19 | false => { 20 | impl Trait for char { 21 | fn is_reference(self) -> bool { 22 | false 23 | } 24 | } 25 | print!("0") 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /questions/015-inference-of-number-type.md: -------------------------------------------------------------------------------- 1 | Answer: 1 2 | Difficulty: 1 3 | 4 | # Hint 5 | 6 | What type would type inference infer for `x`? 7 | 8 | # Explanation 9 | 10 | During type inference the variable `x` has type `&{integer}`, a reference to 11 | some as yet undetermined integer type. 12 | 13 | If we want to resolve the trait method call `Trait::f(x)`, we find that its 14 | argument `x` must be of type `&Self` for some type `Self` that implements 15 | `Trait`. We find that inferring `0: u32` satisfies both the constraint that 16 | `u32` is an integer as well as `u32` implements `Trait`, so the method call ends 17 | up calling `::f(x)` and prints `1`. 18 | 19 | Trait method resolution is covered in more detail in [this Stack Overflow 20 | answer][SO]. 21 | 22 | [SO]: https://stackoverflow.com/a/28552082/6086311 23 | -------------------------------------------------------------------------------- /questions/015-inference-of-number-type.rs: -------------------------------------------------------------------------------- 1 | trait Trait { 2 | fn f(&self); 3 | } 4 | 5 | impl Trait for u32 { 6 | fn f(&self) { 7 | print!("1"); 8 | } 9 | } 10 | 11 | impl<'a> Trait for &'a i32 { 12 | fn f(&self) { 13 | print!("2"); 14 | } 15 | } 16 | 17 | fn main() { 18 | let x = &0; 19 | x.f(); 20 | } 21 | -------------------------------------------------------------------------------- /questions/016-prefix-decrement.md: -------------------------------------------------------------------------------- 1 | Answer: 44 2 | Difficulty: 1 3 | Warnings: double_negations, unused_must_use, unused_mut 4 | 5 | # Hint 6 | 7 | The set of operators supported by Rust is documented in [`std::ops`]. 8 | 9 | [`std::ops`]: https://doc.rust-lang.org/std/ops/index.html 10 | 11 | # Explanation 12 | 13 | Unlike C or Java, there is no unary increment or decrement operator in Rust. The 14 | Rust language design FAQ (no longer available online) used to touch on the 15 | reason: 16 | 17 | > **Why doesn't Rust have increment and decrement operators?**
18 | > Preincrement and postincrement (and the decrement equivalents), while 19 | > convenient, are also fairly complex. They require knowledge of evaluation 20 | > order, and often lead to subtle bugs and undefined behavior in C and C++. `x = 21 | > x + 1` or `x += 1` is only slightly longer, but unambiguous. 22 | 23 | In the absence of a decrement operator, `--x` is parsed as `-(-x)`. In the case 24 | of `x = 4` this would be `-(-4)` which is `4`. The program is equivalent to: 25 | 26 | ```rust 27 | fn main() { 28 | let mut x = 4; 29 | 4; 30 | print!("{}{}", 4, 4); 31 | } 32 | ``` 33 | -------------------------------------------------------------------------------- /questions/016-prefix-decrement.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | let mut x = 4; 3 | --x; 4 | print!("{}{}", --x, --x); 5 | } 6 | -------------------------------------------------------------------------------- /questions/017-unary-decrement.md: -------------------------------------------------------------------------------- 1 | Answer: 2 2 | Difficulty: 1 3 | Warnings: double_negations, unused_mut 4 | 5 | # Hint 6 | 7 | The set of operators supported by Rust is documented in [`std::ops`]. 8 | 9 | [`std::ops`]: https://doc.rust-lang.org/std/ops/index.html 10 | 11 | # Explanation 12 | 13 | Unlike C or Java, there is no unary increment or decrement operator in Rust. The 14 | Rust language design FAQ (no longer available online) used to touch on the 15 | reason: 16 | 17 | > **Why doesn't Rust have increment and decrement operators?**
18 | > Preincrement and postincrement (and the decrement equivalents), while 19 | > convenient, are also fairly complex. They require knowledge of evaluation 20 | > order, and often lead to subtle bugs and undefined behavior in C and C++. `x = 21 | > x + 1` or `x += 1` is only slightly longer, but unambiguous. 22 | 23 | In the absence of postfix and prefix decrement operators, `a-- - --b` is parsed 24 | as `a - (-(-(-(-b))))`. In the case of `a = 5` and `b = 3` the value of this 25 | expression is `5 - 3` which is `2`. 26 | -------------------------------------------------------------------------------- /questions/017-unary-decrement.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | let mut a = 5; 3 | let mut b = 3; 4 | print!("{}", a-- - --b); 5 | } 6 | -------------------------------------------------------------------------------- /questions/018-method-or-function-pointer.md: -------------------------------------------------------------------------------- 1 | Answer: 1 2 | Difficulty: 1 3 | Warnings: dead_code 4 | 5 | # Hint 6 | 7 | The call `.f()` resolves to either the field `f` or the inherent method `f`. How 8 | would you write a call to the other one? 9 | 10 | # Explanation 11 | 12 | A call that looks like `.f()` always resolves to a method, in this case the 13 | inherent method `S::f`. If there were no method `f` in scope, a call like this 14 | would fail to compile even if a field `f` exists and contains a function 15 | pointer. 16 | 17 | To call the function pointer stored in field `f`, we would need to write 18 | parentheses around the field access: 19 | 20 | ```rust 21 | fn main() { 22 | let print2 = || print!("2"); 23 | (S { f: print2 }.f)(); 24 | } 25 | ``` 26 | -------------------------------------------------------------------------------- /questions/018-method-or-function-pointer.rs: -------------------------------------------------------------------------------- 1 | struct S { 2 | f: fn(), 3 | } 4 | 5 | impl S { 6 | fn f(&self) { 7 | print!("1"); 8 | } 9 | } 10 | 11 | fn main() { 12 | let print2 = || print!("2"); 13 | S { f: print2 }.f(); 14 | } 15 | -------------------------------------------------------------------------------- /questions/019-dropped-by-underscore.md: -------------------------------------------------------------------------------- 1 | Answer: 21 2 | Difficulty: 1 3 | 4 | # Hint 5 | 6 | Does `s` get moved? 7 | 8 | # Explanation 9 | 10 | The relevant line is `let _ = s`. If this line does not move `s` then `s` will 11 | continue to live until the close curly brace and the program would print `21`. 12 | But if this line does move `s`, without binding it, then the moved value of type 13 | `S` would be dropped immediately and the program would print `12`. 14 | 15 | In fact `s` does not get moved and the output is `21`. 16 | -------------------------------------------------------------------------------- /questions/019-dropped-by-underscore.rs: -------------------------------------------------------------------------------- 1 | struct S; 2 | 3 | impl Drop for S { 4 | fn drop(&mut self) { 5 | print!("1"); 6 | } 7 | } 8 | 9 | fn main() { 10 | let s = S; 11 | let _ = s; 12 | print!("2"); 13 | } 14 | -------------------------------------------------------------------------------- /questions/020-break-return-in-condition.md: -------------------------------------------------------------------------------- 1 | Answer: 121 2 | Difficulty: 2 3 | Warnings: unreachable_code, unused_braces 4 | 5 | # Hint 6 | 7 | The Rust grammar involving `break` is different from the grammar involving 8 | `return`. 9 | 10 | # Explanation 11 | 12 | Let's work through the functions one at a time. 13 | 14 | - `fn return1` 15 | 16 | The condition of the `if`-statement is parsed as a return-expression that 17 | returns the value `{ print!("1") }` of type `()`. The value needs to be 18 | evaluated prior to being returned so this function prints `1`. 19 | 20 | - `fn return2` 21 | 22 | This function is parsed the same as `return1`. The `return` keyword eagerly 23 | consumes a trailing return value, even if the return value begins with a 24 | curly brace, and even in the condition of an `if`-statement where curly 25 | braces such as in a struct literal would ordinarly not be accepted. This 26 | function prints `2`. 27 | 28 | - `fn break1` 29 | 30 | The condition of the `if`-statement is a break-with-value expression that 31 | breaks out of the enclosing loop with the value `{ print!("1") }` of type 32 | `()`. Similar to `return1`, in order to break with this value the value 33 | needs to be evaluated and this function prints `1`. 34 | 35 | - `fn break2` 36 | 37 | Here we observe a difference between the grammar of `break` and the grammar 38 | of `return`. Unlike `return`, the `break` keyword in the condition of this 39 | `if`-statement *does not* eagerly parse a value that begins with a curly 40 | brace. This code is parsed as: 41 | 42 | ```rust 43 | loop { 44 | if break { 45 | print!("2") 46 | } 47 | {} 48 | } 49 | ``` 50 | 51 | We break out of the loop before executing the print, so this function does 52 | not print anything. 53 | 54 | I believe the reason for the difference between `return` and `break` is that 55 | returning a value was obviously supported at Rust 1.0 and well before, but 56 | break-with-value was introduced fairly late, in [Rust 1.19]. The code in 57 | `break2` was perfectly legal Rust code prior to Rust 1.19 so we cannot 58 | change its behavior when implementing the break-with-value language feature. 59 | 60 | It is possible that a future Edition would adjust the two grammars to align 61 | with each other. 62 | 63 | [Rust 1.19]: https://blog.rust-lang.org/2017/07/20/Rust-1.19.html 64 | 65 | The output from `main` is `121`. 66 | -------------------------------------------------------------------------------- /questions/020-break-return-in-condition.rs: -------------------------------------------------------------------------------- 1 | fn return1() { 2 | if (return { print!("1") }) { 3 | } 4 | } 5 | 6 | fn return2() { 7 | if return { print!("2") } { 8 | } 9 | } 10 | 11 | fn break1() { 12 | loop { 13 | if (break { print!("1") }) { 14 | } 15 | } 16 | } 17 | 18 | fn break2() { 19 | loop { 20 | if break { print!("2") } { 21 | } 22 | } 23 | } 24 | 25 | fn main() { 26 | return1(); 27 | return2(); 28 | break1(); 29 | break2(); 30 | } 31 | -------------------------------------------------------------------------------- /questions/021-closure-or-logical-or.md: -------------------------------------------------------------------------------- 1 | Answer: 221111 2 | Difficulty: 2 3 | Warnings: unreachable_code, unused_must_use, unused_parens 4 | 5 | # Hint 6 | 7 | The `break` and `return` keywords have the same grammar in this question. 8 | 9 | # Explanation 10 | 11 | We want to know whether each possible parenthesization of `return || true;` and 12 | `break || true;` evaluates to the closure `|| true` or to the unit value `()`. 13 | 14 | - `let x = || { (return) || true; };` 15 | 16 | On this line, `x` is a closure that returns `()`. It is equivalent to `let x 17 | = || {}`. When we call `x().f()`, the method `f` resolves to `impl Trait for 18 | ()` which prints `2`. 19 | 20 | The type of the *expression* `(return)` is the primitive [never] type, 21 | usually written as `!`. It is legal to compute `! || true` because `!` can 22 | fill in for any type, in this case bool. The expression `! || true` is a 23 | logical-OR with bool on both the left-hand side and right-hand side. 24 | 25 | The behavior of `!` of filling in for any type is what allows us to write: 26 | 27 | ```rust 28 | fn f() -> bool { 29 | unimplemented!() 30 | } 31 | ``` 32 | 33 | in which the type of `unimplemented!()`, since it panics without evaluating 34 | to any value, is also `!`. 35 | 36 | [never]: https://doc.rust-lang.org/std/primitive.never.html 37 | 38 | - `let x = loop { (break) || true; };` 39 | 40 | Similar to `(return)`, the type of `(break)` is the never type `!`. This 41 | code breaks out of the loop with the implicit value `()`, so `x` is of type 42 | `()`. Calling `x.f()` will print `2`. 43 | 44 | - `let x = || { return (|| true); };` 45 | 46 | On this line `x` is a closure that returns a closure that returns `true`. 47 | You could write `x()()` and that would be `true`. 48 | 49 | The quiz code calls `x().f()` which resolves to `impl Trait for F where 50 | F: FnOnce() -> bool`. That trait impl prints `1`. 51 | 52 | - `let x = loop { break (|| true); };` 53 | 54 | This is a loop containing a break-with-value expression. The argument of the 55 | `break` becomes the value of the enclosing `loop`. This code is equivalent 56 | to `let x = || true`. 57 | 58 | When we call `x.f()` it uses the `FnOnce` impl of `Trait` which prints `1`. 59 | 60 | - `let x = || { return || true; };` 61 | 62 | Now we arrive at the meat of this quiz question. Is `return || true` parsed 63 | the same as `(return) || true` or as `return (|| true)`? 64 | 65 | It turns out to be the latter, so `x` is a closure that returns a closure 66 | that returns true. `x().f()` prints `1`. 67 | 68 | - `let x = loop { break || true; };` 69 | 70 | Similar question here, is this `(break) || true` or `break (|| true)`? 71 | 72 | The break-with-value language feature was added to Rust more than two years 73 | after 1.0, in [Rust 1.19]. Prior to break-with-value, `break || true` was 74 | perfectly legal Rust code that parsed as `(break) || true`. 75 | 76 | In Rust 1.19 the behavior of this code was unintentionally broken by the 77 | language such that now it parses as `break (|| true)` and the printed value 78 | is `1`. 79 | 80 | If we had noticed this change in meaning during the development of Rust 81 | 1.19, we may have adjusted the parsing to preserve the meaning of existing 82 | code. Unfortunately doing so would result in a grammar that behaves 83 | differently between `return` and `break` for no justifiable reason other 84 | than an accident of history. 85 | 86 | Or it is possible we would have ruled this an edge case of syntax that would 87 | never appear in real code, used [Crater] to validate that hypothesis, and 88 | broken the behavior intentionally. 89 | 90 | [Rust 1.19]: https://blog.rust-lang.org/2017/07/20/Rust-1.19.html 91 | [Crater]: https://github.com/rust-lang-nursery/crater 92 | 93 | The total output from `main` is `221111`. 94 | -------------------------------------------------------------------------------- /questions/021-closure-or-logical-or.rs: -------------------------------------------------------------------------------- 1 | trait Trait { 2 | fn f(&self); 3 | } 4 | 5 | impl bool> Trait for F { 6 | fn f(&self) { 7 | print!("1"); 8 | } 9 | } 10 | 11 | impl Trait for () { 12 | fn f(&self) { 13 | print!("2"); 14 | } 15 | } 16 | 17 | fn main() { 18 | let x = || { (return) || true; }; 19 | x().f(); 20 | 21 | let x = loop { (break) || true; }; 22 | x.f(); 23 | 24 | let x = || { return (|| true); }; 25 | x().f(); 26 | 27 | let x = loop { break (|| true); }; 28 | x.f(); 29 | 30 | let x = || { return || true; }; 31 | x().f(); 32 | 33 | let x = loop { break || true; }; 34 | x.f(); 35 | } 36 | -------------------------------------------------------------------------------- /questions/022-macro-tokenize-number.md: -------------------------------------------------------------------------------- 1 | Answer: 22222 2 | Difficulty: 1 3 | 4 | # Hint 5 | 6 | The macro is counting how many "tokens" are in its input. 7 | 8 | # Explanation 9 | 10 | All five invocations of `m!` pass two tokens as input: a minus sign followed by 11 | an integer or floating point literal token. 12 | 13 | The floating point literals `1.`, `1.0`, `1.0e1`, `1.0e-1` are each a single 14 | atomic token. 15 | 16 | The parser built into the Rust compiler always parses a negative sign as a 17 | separate token from the numeric literal that is being negating. However, it is 18 | possible for a user-defined parser within a [procedural macro] to construct a 19 | negative number as a single token by passing a negative integer or negative 20 | floating point value to one of the constructors of [`proc_macro::Literal`]. If 21 | such a negative literal ends up in the input of a subsequent procedural macro 22 | invocation, it is up to the compiler whether to rewrite into a pair of tokens or 23 | keep them as one. 24 | 25 | [procedural macro]: https://github.com/dtolnay/syn 26 | [`proc_macro::Literal`]: https://doc.rust-lang.org/proc_macro/struct.Literal.html 27 | 28 | The behavior of the compiler's parser is observable in the surface language as 29 | well, not only in macros. For example the following code prints `-81` because 30 | the expression is parsed as `-(3i32.pow(4))` rather than `(-3i32).pow(4)`. 31 | 32 | ```rust 33 | fn main() { 34 | let n = -3i32.pow(4); 35 | println!("{}", n); 36 | } 37 | ``` 38 | -------------------------------------------------------------------------------- /questions/022-macro-tokenize-number.rs: -------------------------------------------------------------------------------- 1 | macro_rules! m { 2 | ($a:tt) => { print!("1") }; 3 | ($a:tt $b:tt) => { print!("2") }; 4 | ($a:tt $b:tt $c:tt) => { print!("3") }; 5 | ($a:tt $b:tt $c:tt $d:tt) => { print!("4") }; 6 | ($a:tt $b:tt $c:tt $d:tt $e:tt) => { print!("5") }; 7 | ($a:tt $b:tt $c:tt $d:tt $e:tt $f:tt) => { print!("6") }; 8 | ($a:tt $b:tt $c:tt $d:tt $e:tt $f:tt $g:tt) => { print!("7") }; 9 | } 10 | 11 | fn main() { 12 | m!(-1); 13 | m!(-1.); 14 | m!(-1.0); 15 | m!(-1.0e1); 16 | m!(-1.0e-1); 17 | } 18 | -------------------------------------------------------------------------------- /questions/023-inherent-vs-trait-method.md: -------------------------------------------------------------------------------- 1 | Answer: 12 2 | Difficulty: 2 3 | Warnings: dead_code 4 | 5 | # Hint 6 | 7 | I can't help you with this one. This is a pretty arbitrary choice made by the 8 | language. Try all the possibilities! 9 | 10 | # Explanation 11 | 12 | `S.f()` calls the inherent method `f`. If an inherent method and a trait method 13 | have the same name and receiver type, plain method call syntax will always 14 | prefer the inherent method. The caller would need to write `Trait::f(&S)` or `::f(&S)` in order to call the trait method. 16 | 17 | It is important for macro authors to be aware of this. Macro-generated code 18 | typically should not use method call syntax to invoke trait methods on types 19 | defined by the user. Those calls could get unintentionally hijacked by inherent 20 | methods having the same name as the trait method. 21 | 22 | On the other hand, `S.g()` calls the trait method `g`. Auto-ref during method 23 | resolution always prefers making something into `&` over making it into `&mut` 24 | where either one would work. 25 | 26 | See [this Stack Overflow answer][SO] for a more detailed explanation of auto-ref 27 | during method resolution. 28 | 29 | [SO]: https://stackoverflow.com/a/28552082/6086311 30 | -------------------------------------------------------------------------------- /questions/023-inherent-vs-trait-method.rs: -------------------------------------------------------------------------------- 1 | trait Trait { 2 | fn f(&self); 3 | fn g(&self); 4 | } 5 | 6 | struct S; 7 | 8 | impl S { 9 | fn f(&self) { 10 | print!("1"); 11 | } 12 | 13 | fn g(&mut self) { 14 | print!("1"); 15 | } 16 | } 17 | 18 | impl Trait for S { 19 | fn f(&self) { 20 | print!("2"); 21 | } 22 | 23 | fn g(&self) { 24 | print!("2"); 25 | } 26 | } 27 | 28 | fn main() { 29 | S.f(); 30 | S.g(); 31 | } 32 | -------------------------------------------------------------------------------- /questions/024-local-and-const-hygiene.md: -------------------------------------------------------------------------------- 1 | Answer: 14 2 | Difficulty: 1 3 | Warnings: dead_code, unused_variables 4 | 5 | # Hint 6 | 7 | Hygiene in `macro_rules!` only applies to local variables. 8 | 9 | # Explanation 10 | 11 | This program prints `14` because hygiene in `macro_rules!` only applies to local 12 | variables. 13 | 14 | You can imagine hygiene as a way of assigning a color to each mention of the 15 | name of a local variable, allowing for there to be multiple distinguishable 16 | local variables in scope simultaneously with the same name. 17 | 18 | At the top of `main`, suppose we consider the name of the local variable `x` to 19 | be a purple `x`. The name of the constant `K` is just plain `K`, as constants 20 | are considered items rather than local variables (you can place items outside of 21 | a function body; you cannot place local variables outside of a function body). 22 | 23 |
let x: u8 = 1;
24 | const K: u8 = 2;
25 | 26 | Continuing down the body of `main`, within the declaration of the macro `m!` 27 | there are identifiers `x` and `K` being used. Since there is a local variable 28 | `x` in scope, the use of the identifier `x` within the macro body picks up the 29 | same color as the local variable `x`. There is no local variable `K` in scope so 30 | the `K` within the declaration of the macro is assigned some new color, say 31 | orange. 32 | 33 |
macro_rules! m {
34 |     () => {
35 |         print!("{}{}", x, K);
36 |     };
37 | }
38 | 39 | Next we enter a new scope (delimited by curly braces) containing another `x` and 40 | `K`. Every new local variable always introduces a new color so let's call this 41 | `x` blue. The const again is not a local variable so no color is assigned to 42 | `K`. 43 | 44 |
{
45 |     let x: u8 = 3;
46 |     const K: u8 = 4;
47 | 
48 |     m!();
49 | }
50 | 51 | When `m!()` expands, the expanded code refers to a purple `x` and an orange `K`. 52 | The purple `x` is distinguishable from the blue `x` -- the value of the purple 53 | `x` is printed which is `1`. As for the `K`, an unhygienic (uncolored) `K` is 54 | allowed to act like any color. The second `K` is shadowing the first one. It 55 | gets picked up when looking for an orange `K` and its value is printed, which is 56 | `4`. 57 | 58 | So the output of the quiz code is `14`. 59 | -------------------------------------------------------------------------------- /questions/024-local-and-const-hygiene.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | let x: u8 = 1; 3 | const K: u8 = 2; 4 | 5 | macro_rules! m { 6 | () => { 7 | print!("{}{}", x, K); 8 | }; 9 | } 10 | 11 | { 12 | let x: u8 = 3; 13 | const K: u8 = 4; 14 | 15 | m!(); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /questions/025-unit-infallible-match.md: -------------------------------------------------------------------------------- 1 | Answer: 212 2 | Difficulty: 1 3 | 4 | # Hint 5 | 6 | Figure out what values are owned by which variables where. A value is dropped 7 | when it no longer has an owner. 8 | 9 | # Explanation 10 | 11 | This program prints `212`. 12 | 13 | No value of type `S` gets dropped within the body of function `f`. The function 14 | `f` conjures an `S` and returns ownership of it to the caller of `f`; the caller 15 | determines when to drop the `S` of which it received ownership. 16 | 17 | On the first line of `main`, we call `f()` and perform an infallible match that 18 | binds no new variables. As no variables are declared on this line, there is no 19 | variable that could be the owner of the `S` returned by `f()` so that `S` is 20 | dropped at that point, printing `2`. The `S` in `let S = f()` is a unit struct 21 | pattern (not a variable name) that matches a value of type `S` via 22 | [destructuring] but does not bind the value to any variable. 23 | 24 | [destructuring]: https://doc.rust-lang.org/book/ch18-03-pattern-syntax.html#destructuring-to-break-apart-values 25 | 26 | The second line of `main` conjures a new `S`, prints it, and drops it at the 27 | semicolon. 28 | -------------------------------------------------------------------------------- /questions/025-unit-infallible-match.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::{self, Display}; 2 | 3 | struct S; 4 | 5 | impl Display for S { 6 | fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { 7 | formatter.write_str("1") 8 | } 9 | } 10 | 11 | impl Drop for S { 12 | fn drop(&mut self) { 13 | print!("2"); 14 | } 15 | } 16 | 17 | fn f() -> S { 18 | S 19 | } 20 | 21 | fn main() { 22 | let S = f(); 23 | print!("{}", S); 24 | } 25 | -------------------------------------------------------------------------------- /questions/026-iterator-lazy-map.md: -------------------------------------------------------------------------------- 1 | Answer: 112031 2 | Difficulty: 1 3 | 4 | # Hint 5 | 6 | Refer to the documentation of the [`Iterator`] trait. 7 | 8 | [`Iterator`]: https://doc.rust-lang.org/std/iter/trait.Iterator.html 9 | 10 | # Explanation 11 | 12 | As described in the documentation of the [`Iterator::map`] method, the map 13 | operation is performed lazily. The closure provided as an argument to `map` is 14 | only invoked as values are consumed from the resulting iterator. The closure is 15 | not applied eagerly to the entire input stream up front. 16 | 17 | [`Iterator::map`]: https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.map 18 | 19 | In this code, the `for` loop is what drives the iteration. For each element 20 | consumed from the `parity` iterator, our closure needs to be evaluated one time. 21 | Thus the output will alternate between numbers printed by the closure and 22 | numbers printed by the loop body. 23 | -------------------------------------------------------------------------------- /questions/026-iterator-lazy-map.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | let input = vec![1, 2, 3]; 3 | 4 | let parity = input 5 | .iter() 6 | .map(|x| { 7 | print!("{}", x); 8 | x % 2 9 | }); 10 | 11 | for p in parity { 12 | print!("{}", p); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /questions/027-subtrait-dispatch.md: -------------------------------------------------------------------------------- 1 | Answer: 11 2 | Difficulty: 1 3 | Warnings: dead_code 4 | 5 | # Hint 6 | 7 | `Base::method` and `Derived::method` happen to have the same name but are 8 | otherwise unrelated methods. One does not override the other. 9 | 10 | # Explanation 11 | 12 | The two traits `Base` and `Derived` each define a trait method called `method`. 13 | These methods happen to have the same name but are otherwise unrelated methods 14 | as explained below. 15 | 16 | Both traits provide a default implementation of their trait method. Default 17 | implementations are conceptually copied into each trait impl that does not 18 | explicitly define the same method. In this case for example `impl Base for 19 | BothTraits` does not provide its own implementation of `Base::method`, which 20 | means the implementation of `Base` for `BothTraits` will use the default 21 | behavior defined by the trait i.e. `print!("1")`. 22 | 23 | Additionally, `Derived` has `Base` as a _supertrait_ which means that every type 24 | that implements `Derived` is also required to implement `Base`. The two trait 25 | methods are unrelated despite having the same name -- thus any type that 26 | implements `Derived` will have an implementation of `Derived::method` as well as 27 | an implementation of `Base::method` and the two are free to have different 28 | behavior. Supertraits are not inheritance! Supertraits are a constraint that if 29 | some trait is implemented, some other trait must also be implemented. 30 | 31 | Let's consider what happens in each of the two methods called from `main`. 32 | 33 | - `dynamic_dispatch(&BothTraits)` 34 | 35 | The argument `x` is a reference to the trait object type `dyn Base`. A 36 | _trait object_ is a little shim generated by the compiler that implements 37 | the trait with the same name by forwarding all trait method calls to trait 38 | methods of whatever type the trait object was created from. The forwarding 39 | is done by reading from a table of function pointers contained within the 40 | trait object. 41 | 42 | ```rust 43 | // Generated by the compiler. 44 | // 45 | // This is an implementation of the trait `Base` for the 46 | // trait object type `dyn Base`, which you can think of as 47 | // a struct containing function pointers. 48 | impl Base for (dyn Base) { 49 | fn method(&self) { 50 | /* 51 | Some automatically generated implementation detail 52 | that ends up calling the right type's impl of the 53 | trait method Base::method. 54 | */ 55 | } 56 | } 57 | ``` 58 | 59 | In the quiz code, `x.method()` is a call to this automatically generated 60 | method whose fully qualified name is `::method`. Since `x` 61 | was obtained by converting a `BothTraits` to `dyn Base`, the automatically 62 | generated implementation detail will wind up forwarding to `::method` which prints `1`. 64 | 65 | Hopefully it's clear from all of this that nothing here has anything to do 66 | with the unrelated trait method `Derived::method` defined by `BothTraits`. 67 | Especially notice that `x.method()` cannot be a call to `Derived::method` 68 | because `x` is of type `dyn Base` and there is no implementation of 69 | `Derived` for `dyn Base`. 70 | 71 | - `static_dispatch(BothTraits)` 72 | 73 | At compile time we know that `x.method()` is a call to `::method`. Type inference within generic functions in Rust happens 75 | independently of any concrete instantiation of the generic function i.e. 76 | before we know what `T` may be, other than the fact that it implements 77 | `Base`. Thus no inherent method on the concrete type `T` or any other trait 78 | method may affect what method `x.method()` is calling. By the time that `T` 79 | is decided, it has already been determined that `x.method()` is calling `::method`. 81 | 82 | The generic function is instantiated with `T` equal to `BothTraits` so this 83 | is going to call `::method` which prints `1`. 84 | 85 | If you are familiar with C++, the behavior of this code in Rust is _different_ 86 | from the behavior of superficially analogous C++ code. In C++ the output would 87 | be `22` as seen in the following implementation. This highlights the difference 88 | between Rust's traits and supertraits vs C++'s inheritance. 89 | 90 | ```cpp 91 | #include 92 | 93 | struct Base { 94 | virtual void method() const { 95 | std::cout << "1"; 96 | } 97 | }; 98 | 99 | struct Derived: Base { 100 | void method() const { 101 | std::cout << "2"; 102 | } 103 | }; 104 | 105 | void dynamic_dispatch(const Base &x) { 106 | x.method(); 107 | } 108 | 109 | template 110 | void static_dispatch(const T x) { 111 | x.method(); 112 | } 113 | 114 | int main() { 115 | dynamic_dispatch(Derived{}); 116 | static_dispatch(Derived{}); 117 | } 118 | ``` 119 | -------------------------------------------------------------------------------- /questions/027-subtrait-dispatch.rs: -------------------------------------------------------------------------------- 1 | trait Base { 2 | fn method(&self) { 3 | print!("1"); 4 | } 5 | } 6 | 7 | trait Derived: Base { 8 | fn method(&self) { 9 | print!("2"); 10 | } 11 | } 12 | 13 | struct BothTraits; 14 | impl Base for BothTraits {} 15 | impl Derived for BothTraits {} 16 | 17 | fn dynamic_dispatch(x: &dyn Base) { 18 | x.method(); 19 | } 20 | 21 | fn static_dispatch(x: T) { 22 | x.method(); 23 | } 24 | 25 | fn main() { 26 | dynamic_dispatch(&BothTraits); 27 | static_dispatch(BothTraits); 28 | } 29 | -------------------------------------------------------------------------------- /questions/028-underscore-prefix.md: -------------------------------------------------------------------------------- 1 | Answer: 3121 2 | Difficulty: 1 3 | 4 | # Hint 5 | 6 | A value is dropped when it no longer has an owner. 7 | 8 | # Explanation 9 | 10 | The program prints `3121`. That is, the `Drop` impl for `let _guard = Guard` 11 | runs at the end of main but the `Drop` impl for `let _ = Guard` runs right away. 12 | 13 | In general, a value is dropped when it no longer has an owner. The variable 14 | `_guard` owns the first value of type `Guard` and remains in scope until the end 15 | of main. The `_` is not a variable but a wildcard pattern that binds nothing; 16 | since no variables are bound on this line, there is no variable to be the owner 17 | of the second value of type `Guard` and that value is dropped on the same line. 18 | 19 | This distinction between the underscore pattern vs variables with a leading 20 | underscore is incredibly important to remember when working with lock guards in 21 | unsafe code. 22 | 23 | use std::sync::Mutex; 24 | 25 | static MUTEX: Mutex<()> = Mutex::new(()); 26 | 27 | /// MUTEX must be held when accessing this value. 28 | static mut VALUE: usize = 0; 29 | 30 | fn main() { 31 | let _guard = MUTEX.lock().unwrap(); 32 | unsafe { 33 | VALUE += 1; 34 | } 35 | } 36 | 37 | If this code were to use `let _ = MUTEX.lock().unwrap()` then the mutex guard 38 | would be dropped immediately, releasing the mutex and failing to guard the 39 | access of `VALUE`. 40 | -------------------------------------------------------------------------------- /questions/028-underscore-prefix.rs: -------------------------------------------------------------------------------- 1 | struct Guard; 2 | 3 | impl Drop for Guard { 4 | fn drop(&mut self) { 5 | print!("1"); 6 | } 7 | } 8 | 9 | fn main() { 10 | let _guard = Guard; 11 | print!("3"); 12 | let _ = Guard; 13 | print!("2"); 14 | } 15 | -------------------------------------------------------------------------------- /questions/029-tuple-trailing-commas.md: -------------------------------------------------------------------------------- 1 | Answer: 1244 2 | Difficulty: 1 3 | Warnings: unused_parens 4 | 5 | # Hint 6 | 7 | A value in parentheses does not have the same type as a 1-tuple. 8 | 9 | # Explanation 10 | 11 | The trailing comma is required in the case of a 1-tuple, `(0,)`, because it 12 | disambiguates it from `(0)` which is identical to `0`. However, for larger 13 | tuples, it is entirely optional: `(i32)` is a distinct type from `(i32,)`, but 14 | `(i32, i32)` and `(i32, i32,)` are the same. 15 | 16 | An integral literal `0` can be inferred to be any integer type, but defaults to 17 | `i32` if insufficient type information is available. `(0)` is inferred to be a 18 | `u32` and `(0,)` is inferred to be a `(i32,)` because those are respectively the 19 | only integral and 1-tuple types with an implementation for `Trait`. 20 | 21 | Since `(0, 0)` and `(0, 0,)` have the same type, the output of their `p` methods 22 | must be the same, but Rust needs to somehow choose between the two possible 23 | implementations of `Trait`, namely `(u32, u32)` and `(i32, i32)`. Since `i32` is 24 | the default integral type, `(i32, i32)` is chosen in both cases. 25 | -------------------------------------------------------------------------------- /questions/029-tuple-trailing-commas.rs: -------------------------------------------------------------------------------- 1 | trait Trait { 2 | fn p(&self); 3 | } 4 | 5 | impl Trait for (u32) { 6 | fn p(&self) { print!("1"); } 7 | } 8 | 9 | impl Trait for (i32,) { 10 | fn p(&self) { print!("2"); } 11 | } 12 | 13 | impl Trait for (u32, u32) { 14 | fn p(&self) { print!("3"); } 15 | } 16 | 17 | impl Trait for (i32, i32,) { 18 | fn p(&self) { print!("4"); } 19 | } 20 | 21 | fn main() { 22 | (0).p(); 23 | (0,).p(); 24 | (0, 0).p(); 25 | (0, 0,).p(); 26 | } 27 | -------------------------------------------------------------------------------- /questions/030-clone-pointers.md: -------------------------------------------------------------------------------- 1 | Answer: 111011 2 | Difficulty: 1 3 | Warnings: noop_method_call, unused_variables 4 | 5 | # Hint 6 | 7 | Immutable pointers `&T` and `Rc` implement `Clone` even if `T` doesn't. 8 | 9 | # Explanation 10 | 11 | Both of our non-reference types, `()` and `A`, are zero-sized types (ZST). The 12 | function `p` will print `0` if it is passed a value of type `X = ()` or `X = 13 | A`, and it will print `1` if passed a reference `X = &()` or `X = &A` regardless 14 | of exactly how big pointers happen to be. 15 | 16 | `p(a)` invokes `p` with `X = &A` because the argument `a` is of type `&A`; this 17 | prints `1`. 18 | 19 | On the next line, if `A` implemented `Clone` then `a.clone()` would be a call to 20 | that impl. But since it doesn't, the compiler finds another applicable impl 21 | which is the implementation of `Clone` for references `&T` -- so concretely the 22 | clone call is calling the impl of `Clone` for `&A` which turns a `&&A` into a 23 | `&A` by simply duplicating the reference. We get another call to `p` with `X = 24 | &A` printing `1`. The impl of `Clone` for references is useful in practice when 25 | a struct containing a reference wants to derive `Clone`, but as seen here it can 26 | sometimes kick in unexpectedly. 27 | 28 | The type `()` _does_ implement `Clone` so `b.clone()` invokes that impl and 29 | produces `()`. The implementation of `Clone` for `&()` would also be applicable 30 | as happened in the case of `A`, but the compiler prefers calling the trait impl 31 | for `()` which converts `&()` to `()` over the trait impl for `&()` which 32 | converts `&&()` to `&()` because the former is the one that requires fewer 33 | implicit references or dereferences inserted by the trait solver. In the call to 34 | `b.clone()`, `b` is of type `&()` which exactly matches the argument of the impl 35 | `Clone` for `()`, while in order to obtain a `&&()` to pass as argument to the 36 | impl `Clone` for `&()` the trait solver would need to insert an additional layer 37 | of referencing implicitly -- effectively computing `(&b).clone()`. 38 | 39 | What we get is `p(b)` calling `p` with `X = &()` and `p(b.clone())` calling `p` 40 | with `X = ()`. Together these print `10`. 41 | 42 | Finally in the `Rc` case, both calls to `p` are with `X = Rc<()>` which is 43 | non-zero sized. It is considered idiomatic to clone a `Rc` using `Rc::clone(&c)` 44 | instead of `c.clone()` because it makes it apparent that this is a reference 45 | count bump rather than cloning underlying data, but ultimately both refer to the 46 | same function. To call the `clone` method of a value inside a `Rc`, you would 47 | need to dereference it first: `(*c).clone()`. 48 | -------------------------------------------------------------------------------- /questions/030-clone-pointers.rs: -------------------------------------------------------------------------------- 1 | use std::rc::Rc; 2 | 3 | struct A; 4 | 5 | fn p(x: X) { 6 | match std::mem::size_of::() { 7 | 0 => print!("0"), 8 | _ => print!("1"), 9 | } 10 | } 11 | 12 | fn main() { 13 | let a = &A; 14 | p(a); 15 | p(a.clone()); 16 | 17 | let b = &(); 18 | p(b); 19 | p(b.clone()); 20 | 21 | let c = Rc::new(()); 22 | p(Rc::clone(&c)); 23 | p(c.clone()); 24 | } 25 | -------------------------------------------------------------------------------- /questions/031-method-lookup.md: -------------------------------------------------------------------------------- 1 | Answer: 111222 2 | Difficulty: 2 3 | 4 | # Hint 5 | 6 | During a method lookup, Rust automatically derefences and borrows the receiver 7 | in a well-defined order until it finds the first function with a suitable 8 | signature. What is that order? 9 | 10 | # Explanation 11 | 12 | The [Reference][ref] describes Rust's method lookup order. The relevant 13 | paragraph is: 14 | > Obtain [the candidate receiver type] by repeatedly dereferencing the receiver 15 | > expression's type, adding each type encountered to the list, then finally 16 | > attempting an unsized coercion at the end, and adding the result type if that 17 | > is successful. Then, for each candidate `T`, add `&T` and `&mut T` to the 18 | > list immediately after `T`. 19 | 20 | Applying these rules to the given examples, we have: 21 | * `t.f()`: We try to find a function `f` defined on the type `T`, but there is 22 | none. Next, we search the type `&T`, and find the first implementation of the 23 | `Or` trait, and we are done. Upon invocation, the resolved call prints `1`. 24 | * `wt.f()`: We search for a function `f` defined on `&T`, which immediately 25 | succeeds. Upon invocation, the function prints `1`. 26 | * `wwt.f()`: The search order is `&&T` -> `&&&T` -> `&mut &&T` -> `&T`, and 27 | we're done. Upon invocation, the function prints `1`. 28 | * `wwwt.f()`: `&&&T` -> `&&&&T`. This prints `2`. 29 | * `wwwwt.f()`: `&&&&T`. This prints `2`. 30 | * `wwwwwt.f()`: `&&&&&T` -> `&&&&&&T` -> `&mut &&&&&T` -> `&&&&T`. This prints 31 | `2`. 32 | 33 | [ref]: https://doc.rust-lang.org/reference/expressions/method-call-expr.html 34 | -------------------------------------------------------------------------------- /questions/031-method-lookup.rs: -------------------------------------------------------------------------------- 1 | trait Or { 2 | fn f(self); 3 | } 4 | 5 | struct T; 6 | 7 | impl Or for &T { 8 | fn f(self) { 9 | print!("1"); 10 | } 11 | } 12 | 13 | impl Or for &&&&T { 14 | fn f(self) { 15 | print!("2"); 16 | } 17 | } 18 | 19 | fn main() { 20 | let t = T; 21 | let wt = &T; 22 | let wwt = &&T; 23 | let wwwt = &&&T; 24 | let wwwwt = &&&&T; 25 | let wwwwwt = &&&&&T; 26 | t.f(); 27 | wt.f(); 28 | wwt.f(); 29 | wwwt.f(); 30 | wwwwt.f(); 31 | wwwwwt.f(); 32 | } 33 | -------------------------------------------------------------------------------- /questions/032-or-pattern-guard.md: -------------------------------------------------------------------------------- 1 | Answer: 124 2 | Difficulty: 2 3 | 4 | # Hint 5 | 6 | Either way would be confusing in different situations; there isn't a clear right 7 | behavior that a hint could help identify. Guess both. :/ 8 | 9 | # Explanation 10 | 11 | This question covers two behaviors of `match` arms and guards. 12 | 13 | First, whether an `if` guard on a match-arm containing `|` applies to *all* 14 | alternatives in the match-arm or just to the one it is adjacent to. In the quiz 15 | code, does `check(x)` execute at all for `(x, _)` or does it only cover the `(_, 16 | x)` case? We would expect `1` would get printed if and only if the former is the 17 | case. In fact `1` does get printed. A match-arm gets to have at most one `if` 18 | guard and that guard applies to all the `|`-separated alternatives in the arm. 19 | 20 | But second, this question also covers a kind of "backtracking" behavior of 21 | match-arms. After `check(x)` returns false on `(x, _)`, does the whole match-arm 22 | fail to match at that point or does Rust move on to `(_, x)` and execute the 23 | guard a second time? We would expect `2` to be printed if and only if the latter 24 | is the case. In fact `2` does get printed; the guard is being run multiple 25 | times, once per `|`-separated alternative in the match-arm. 26 | -------------------------------------------------------------------------------- /questions/032-or-pattern-guard.rs: -------------------------------------------------------------------------------- 1 | fn check(x: i32) -> bool { 2 | print!("{}", x); 3 | false 4 | } 5 | 6 | fn main() { 7 | match (1, 2) { 8 | (x, _) | (_, x) if check(x) => { 9 | print!("3") 10 | } 11 | _ => print!("4"), 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /questions/033-range-full-method.md: -------------------------------------------------------------------------------- 1 | Answer: 24 2 | Difficulty: 3 3 | 4 | # Hint 5 | 6 | `||` is a closure introducer. `..` is range syntax, normally seen in slicing 7 | operations like `&s[1..4]` or `&s[..s.len() - 1]`. 8 | 9 | # Explanation 10 | 11 | The two rational possibilities are `1` or `24`, depending on how the precedence 12 | of `|| .. .method()` is disambiguated. 13 | 14 | - As `|| ((..).method())`, which is a closure whose body invokes our impl of 15 | `Trait` on `RangeFull`. In this case `main` would print `1`. It would *not* 16 | print `13` because the `fn()` returned from `(..).method()` is never invoked 17 | by `main`. 18 | 19 | - As `(|| ..).method()`, which is an invocation of our impl of `Trait` on 20 | `FnOnce() -> T` where `T` is inferred to be `RangeFull`. In this case `main` 21 | would print `24`. 22 | 23 | The latter of those is the correct answer. 24 | 25 | We can achieve the former behavior by explicitly parenthesizing as shown in the 26 | bullet above. 27 | 28 | Partially parenthesizing as `|| (.. .method())` is not sufficient. This results 29 | in a parse error. 30 | 31 | ``` 32 | error: expected one of `)` or `,`, found `.` 33 | --> src/main.rs:22:13 34 | | 35 | 22 | (|| (.. .method()))(); 36 | | -^ expected one of `)` or `,` 37 | | | 38 | | help: missing `,` 39 | ``` 40 | 41 | Correctly handling a quite ambiguous expression like `|| .. .method()` is a 42 | challenge for tooling, as seen by the associated bugs in Rustfmt 43 | ([rust-lang/rustfmt#4808]) and Syn ([dtolnay/syn#1019]). 44 | 45 | [rust-lang/rustfmt#4808]: https://github.com/rust-lang/rustfmt/issues/4808 46 | [dtolnay/syn#1019]: https://github.com/dtolnay/syn/issues/1019 47 | -------------------------------------------------------------------------------- /questions/033-range-full-method.rs: -------------------------------------------------------------------------------- 1 | use std::ops::RangeFull; 2 | 3 | trait Trait { 4 | fn method(&self) -> fn(); 5 | } 6 | 7 | impl Trait for RangeFull { 8 | fn method(&self) -> fn() { 9 | print!("1"); 10 | || print!("3") 11 | } 12 | } 13 | 14 | impl T, T> Trait for F { 15 | fn method(&self) -> fn() { 16 | print!("2"); 17 | || print!("4") 18 | } 19 | } 20 | 21 | fn main() { 22 | (|| .. .method())(); 23 | } 24 | -------------------------------------------------------------------------------- /questions/034-fn-pointer-vs-fn-type.md: -------------------------------------------------------------------------------- 1 | Answer: 20 2 | Difficulty: 2 3 | 4 | # Hint 5 | 6 | The answer would be the same with any other integer type in place of `u8`. 7 | 8 | # Explanation 9 | 10 | The expression `a::`'s type is a zero-sized type (ZST). 11 | 12 | Rust's implementation choices around function types are different from nearly 13 | all other languages, but are an important enabler of many of Rust's 14 | zero-overhead abstractions. In Rust, every function (or every distinct 15 | instantiation of a generic function) has its own unique type. In particular, 16 | even two functions with the same function signature would have different types. 17 | 18 | Having a unique type for each function allows the type itself to carry the 19 | information of what function will be called, not needing any runtime state such 20 | as a pointer. 21 | 22 | To understand the optimization advantages of this approach, consider 23 | `Iterator::map` and the two calls `iter.map(f)` and `iter.map(g)` where `f` and 24 | `g` are different functions with the same signature. Because `f` and `g` have 25 | distinct types, the two `map` calls would produce two different monomorphic 26 | instantiations of the generic `map` function, one of which statically calls `f` 27 | and the other statically calls `g`, as if you had directly written a 28 | special-purpose map implementation specific to each function without the 29 | abstraction provided by `map`. The generic `map` is thus a zero-overhead 30 | abstraction. Traditionally in other languages such as C++ or Go, in this 31 | situation `f` and `g` would be passed to `map` as a function pointer and there 32 | would be just one instantiation of `map`, containing a dynamic dispatch to 33 | execute the function call, which is usually going to be slower than statically 34 | calling the right function. This performance penalty makes `map` in those 35 | languages not a zero-overhead abstraction. 36 | 37 | Currently in Rust there is no syntax to express the type of a specific function, 38 | so they are always passed as a generic type parameter with a `FnOnce`, `Fn` or 39 | `FnMut` bound. In error messages you might see function types appear in the 40 | form `fn(T) -> U {fn_name}`, but you can't use this syntax in code. 41 | 42 | On the other hand, a function pointer, `fn(T) -> U`, is pointer-sized at 43 | runtime. Function types can be coerced into function pointers, which can be 44 | useful in case you need to defer the choice of function to call until runtime. 45 | 46 | In the quiz code, the first call in `main` coerces `a::` from a function to 47 | a function pointer (`fn(fn(u8)) {a::}` to `fn(fn(u8))`) prior to calling 48 | `d`, so its size would be 8 on a system with 64-bit function pointers. The 49 | second call in `main` does not involve function pointers; `d` is directly called 50 | with `T` being the inexpressible type of `a::`, which is zero-sized. 51 | -------------------------------------------------------------------------------- /questions/034-fn-pointer-vs-fn-type.rs: -------------------------------------------------------------------------------- 1 | fn d(_f: T) { 2 | match std::mem::size_of::() { 3 | 0 => print!("0"), 4 | 1 => print!("1"), 5 | _ => print!("2"), 6 | } 7 | } 8 | 9 | fn a(f: fn(T)) { 10 | d(f); 11 | } 12 | 13 | fn main() { 14 | a(a::); 15 | d(a::); 16 | } 17 | -------------------------------------------------------------------------------- /questions/035-decl-macro-hygiene.md: -------------------------------------------------------------------------------- 1 | Answer: 121 2 | Difficulty: 1 3 | Warnings: unused_variables 4 | 5 | # Hint 6 | 7 | There are some programs for which `cargo expand` produces expanded code that 8 | compiles, but behaves differently than the original code with the original macro 9 | hygiene. 10 | 11 | # Explanation 12 | 13 | There are two reasonable paths to an incorrect answer on this question, based on 14 | your assumptions around how this macro gets expanded: 15 | 16 | 1. `let a = X(2);` 17 | 2. `{ let a = X(2); }` 18 | 19 | If the first expansion were right, the macro would introduce a new binding, `a`, 20 | which shadows the `a` already directly assigned in `main`. So the print 21 | statement in `main` would execute first, printing `2`, then the variables would 22 | drop in reverse order of introduction, printing `2` then `1`, with a final 23 | output of `221`. 24 | 25 | If the second expansion were right, the macro would introduce `a` in a nested 26 | scope, shadowing the already existing `a` only inside of that scope and not 27 | beyond it. Since the new `a`'s scope ends before the print statement, its `Drop` 28 | impl when going out of scope would be the first print to execute, printing `2`. 29 | Next the print in `main` would print `1` which is the value of the first `a`, 30 | and finally `1` again when that value drops at the end of `main`, with final 31 | output `211`. 32 | 33 | If you've read about macro hygiene then you might have guessed it would be 34 | implemented something like this second option. It's important that internals of 35 | a macro don't interfere coincidentally with variables in scope at the call site, 36 | and Rust macros mostly do a good job of preventing unintended name collisions. 37 | However, this is not how hygiene is implemented; introducing artificial scopes 38 | around macro expansions would make them more limited in their usefulness, and 39 | wouldn't solve a lot of other hygiene problems. 40 | 41 | You can instead imagine hygiene as a way of assigning a color to each mention of 42 | the name of a local variable, allowing for there to be multiple distinguishable 43 | local variables in scope simultaneously with the same textual name. 44 | 45 |
fn main() {
46 |     let a = X(1);
47 |     let a = X(2);
48 |     print!("{}", a.0);
49 | }
50 | 51 | So what's printed is the value of `main`'s identifier 52 | a 53 | which is `1`, then the two values are dropped in reverse order of introduction 54 | printing `2` then `1`, and the output of the program is `121`. 55 | -------------------------------------------------------------------------------- /questions/035-decl-macro-hygiene.rs: -------------------------------------------------------------------------------- 1 | macro_rules! x { 2 | ($n:expr) => { 3 | let a = X($n); 4 | }; 5 | } 6 | 7 | struct X(u64); 8 | 9 | impl Drop for X { 10 | fn drop(&mut self) { 11 | print!("{}", self.0); 12 | } 13 | } 14 | 15 | fn main() { 16 | let a = X(1); 17 | x!(2); 18 | print!("{}", a.0); 19 | } 20 | -------------------------------------------------------------------------------- /questions/036-fnmut-copy.md: -------------------------------------------------------------------------------- 1 | Answer: 1223 2 | Difficulty: 1 3 | 4 | # Hint 5 | 6 | The variable `i` is captured by value in the compiler-generated closure object. 7 | 8 | # Explanation 9 | 10 | The object passed into `g` is a `FnMut` closure which captures an integer by 11 | value. Effectively it's an unnameable struct containing a single field whose 12 | type is `i32`, with a function call operator that takes `&mut self`: 13 | 14 | ```rust 15 | #[derive(Copy, Clone)] 16 | pub struct UnnameableClosure { 17 | i: i32, 18 | } 19 | 20 | impl UnnameableClosure { 21 | pub fn unnameable_call_operator(&mut self) { 22 | self.i += 1; 23 | print!("{}", self.i); 24 | } 25 | } 26 | 27 | let mut i = 0i32; 28 | g(UnnameableClosure { i }); 29 | ``` 30 | 31 | The behavior of the 4 calls inside `g` is as follows: 32 | 33 | - `f()` runs the closure and its by-value captured value of `i` becomes 1. 34 | 35 | - `call(f)` makes a **copy** of `f` to become the argument of `call`. The copy 36 | gets executed and its `i` becomes 2, but the original closure still holds a 37 | value of 1 for its captured `i`. The copy of the closure gets dropped as it 38 | goes out of scope at the end of the body of `call`. 39 | 40 | - `f()` runs the original closure a second time and its `i` becomes 2. 41 | 42 | - `call(f)` copies `f` a second time and executes the copy, its `i` becomes 3. 43 | 44 | Since Rust 1.26, closures automatically implement `Clone` if all their captures 45 | implement `Clone`, and `Copy` if all the captures implement `Copy`. 46 | 47 | If the `move` keyword were omitted from the quiz code, the compiler-generated 48 | closure would capture `i` by mutable reference instead of by value: 49 | 50 | ```rust 51 | pub struct UnnameableClosure<'a> { 52 | i: &'a mut i32, 53 | } 54 | ``` 55 | 56 | and there would no longer be a `Copy` impl, because it's incorrect to duplicate 57 | a mutable reference into multiple copies (aliasing xor mutation; this is the 58 | point of the borrow checker). 59 | 60 | One recurring source of confusion for Rust beginners is the relationship between 61 | `move` and non-`move` closures vs `Fn` and `FnMut` and `FnOnce` closures. These 62 | are two nearly-orthogonal things. As illustrated in the `UnnameableClosure` 63 | pseudocode above, `move` vs non-`move` is about whether the *fields* of the 64 | compiler-generated closure struct have the same type as the original captured 65 | variable's type, vs are references to the original captured variable's type 66 | (`i32` vs `&mut i32`, for example). In contrast, `Fn` vs `FnMut` vs `FnOnce` is 67 | about whether the *call method* of the compiler-generated closure struct has a 68 | receiver which is `&self` vs `&mut self` vs `self`. 69 | -------------------------------------------------------------------------------- /questions/036-fnmut-copy.rs: -------------------------------------------------------------------------------- 1 | fn call(mut f: impl FnMut() + Copy) { 2 | f(); 3 | } 4 | 5 | fn g(mut f: impl FnMut() + Copy) { 6 | f(); 7 | call(f); 8 | f(); 9 | call(f); 10 | } 11 | 12 | fn main() { 13 | let mut i = 0i32; 14 | g(move || { 15 | i += 1; 16 | print!("{}", i); 17 | }); 18 | } 19 | -------------------------------------------------------------------------------- /questions/037-lifetime-extension.md: -------------------------------------------------------------------------------- 1 | Answer: 1001 2 | Difficulty: 2 3 | 4 | # Hint 5 | 6 | `let` is a statement, while assignment is an expression. 7 | 8 | # Explanation 9 | 10 | In both cases, since we don't assign the `Drop0` instance to a variable, it is a 11 | [temporary]. 12 | 13 | In `let` statements, [temporary lifetime extension][tle] takes place and extends 14 | the temporary's lifetime until the end of the block, there it is dropped. So `1` 15 | is printed first, and then the `Drop0` is dropped and `0` is printed. 16 | 17 | In assignments, however (`_ = ` is a [destructuring assignment][des_assign] 18 | expression), there is no temporary lifetime extension, and temporaries are 19 | dropped at the end of the statement. So, `0` is printed first then `1`. 20 | 21 | This behavior also means that if we would try to use the value after the 22 | assignment, the compiler will disallow this with a borrow checker error, as the 23 | value was already dropped. 24 | 25 | [temporary]: https://doc.rust-lang.org/stable/reference/expressions.html#temporaries 26 | [tle]: https://doc.rust-lang.org/stable/reference/destructors.html#temporary-lifetime-extension 27 | [des_assign]: https://rust-lang.github.io/rfcs/2909-destructuring-assignment.html 28 | -------------------------------------------------------------------------------- /questions/037-lifetime-extension.rs: -------------------------------------------------------------------------------- 1 | struct Drop0; 2 | impl Drop for Drop0 { 3 | fn drop(&mut self) { 4 | print!("0"); 5 | } 6 | } 7 | 8 | fn main() { 9 | { 10 | let _ = &Drop0; 11 | print!("1"); 12 | } 13 | { 14 | _ = &Drop0; 15 | print!("1"); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /rust-quiz: -------------------------------------------------------------------------------- 1 | docs -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dtolnay/rust-quiz/05c5f18d9fe1b12b57333e9fbdc107cdc1ca56aa/screenshot.png -------------------------------------------------------------------------------- /src/error.rs: -------------------------------------------------------------------------------- 1 | use rayon::ThreadPoolBuildError; 2 | use std::fmt::{self, Display}; 3 | use std::io; 4 | use std::path::PathBuf; 5 | use std::string::FromUtf8Error; 6 | use thiserror::Error; 7 | 8 | #[remain::sorted] 9 | #[derive(Error, Debug)] 10 | pub enum Error { 11 | #[error("program compiled with warnings; make sure every expected warning is listed in a 'Warnings:' section")] 12 | CompiledWithWarnings, 13 | 14 | #[error("failed to execute quiz question: {0}")] 15 | Execute(io::Error), 16 | 17 | #[error("wrong filename format")] 18 | FilenameFormat, 19 | 20 | #[error(transparent)] 21 | Http(#[from] http::Error), 22 | 23 | #[error(transparent)] 24 | Io(#[from] io::Error), 25 | 26 | #[error(transparent)] 27 | Json(#[from] serde_json::Error), 28 | 29 | #[error( 30 | "{0} does not match the expected format.\n{markdown}", 31 | markdown = crate::render::MARKDOWN_FORMAT 32 | )] 33 | MarkdownFormat(PathBuf), 34 | 35 | #[error("program compiled without expected warning: {}", CommaSep(.0))] 36 | MissingExpectedWarning(Vec), 37 | 38 | #[error(transparent)] 39 | Rayon(ThreadPoolBuildError), 40 | 41 | #[error("failed to execute rustc: {0}")] 42 | Rustc(io::Error), 43 | 44 | #[error("program failed to compile")] 45 | ShouldCompile, 46 | 47 | #[error("program should fail to compile")] 48 | ShouldNotCompile, 49 | 50 | #[error("program with undefined behavior should compile")] 51 | UndefinedShouldCompile, 52 | 53 | #[error(transparent)] 54 | Utf8(#[from] FromUtf8Error), 55 | 56 | #[error("wrong output! expected: {expected}, actual: {output}")] 57 | WrongOutput { expected: String, output: String }, 58 | } 59 | 60 | struct CommaSep<'a>(&'a [String]); 61 | 62 | impl<'a> Display for CommaSep<'a> { 63 | fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { 64 | for (i, string) in self.0.iter().enumerate() { 65 | if i > 0 { 66 | formatter.write_str(", ")?; 67 | } 68 | formatter.write_str(string)?; 69 | } 70 | Ok(()) 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | #![allow( 2 | clippy::elidable_lifetime_names, 3 | clippy::let_underscore_untyped, 4 | clippy::match_bool, 5 | clippy::needless_lifetimes, 6 | clippy::needless_return, // https://github.com/tokio-rs/tokio/issues/6869 7 | clippy::uninlined_format_args 8 | )] 9 | 10 | mod error; 11 | mod render; 12 | mod serve; 13 | 14 | use crate::error::Error; 15 | use clap::{Parser as ClapParser, Subcommand as ClapSubcommand}; 16 | use oqueue::{Color::Red, Sequencer}; 17 | use std::io::{self, Write}; 18 | use std::process; 19 | 20 | const HELP: &str = "\ 21 | {about} 22 | {author} 23 | 24 | {usage-heading} {usage} 25 | 26 | {all-args}\ 27 | "; 28 | 29 | #[derive(ClapParser, Debug)] 30 | #[command( 31 | about = "Rust Quiz", 32 | version, 33 | author, 34 | help_template = HELP, 35 | disable_help_subcommand = true, 36 | )] 37 | struct Opt { 38 | #[clap(subcommand)] 39 | subcommand: Option, 40 | } 41 | 42 | #[derive(ClapSubcommand, Debug)] 43 | enum Subcommand { 44 | /// Serve website over http at localhost:8000 45 | Serve, 46 | } 47 | 48 | fn report(result: Result<(), Error>) { 49 | if let Err(err) = result { 50 | let task = Sequencer::stderr().begin(); 51 | task.bold_color(Red); 52 | write!(task, "ERROR"); 53 | task.bold(); 54 | writeln!(task, ": {}", err); 55 | task.reset_color(); 56 | process::exit(1); 57 | } 58 | } 59 | 60 | #[tokio::main] 61 | async fn main() { 62 | let opt = Opt::parse(); 63 | 64 | report(render::main()); 65 | 66 | match opt.subcommand { 67 | None => {} 68 | Some(Subcommand::Serve) => { 69 | let _ = writeln!(io::stderr()); 70 | report(serve::main().await); 71 | } 72 | } 73 | } 74 | 75 | #[test] 76 | fn test_cli() { 77 | ::command().debug_assert(); 78 | } 79 | -------------------------------------------------------------------------------- /src/render.rs: -------------------------------------------------------------------------------- 1 | use crate::error::Error; 2 | use oqueue::{Color::Red, Sequencer}; 3 | use parking_lot::Mutex; 4 | use pulldown_cmark::{html as markdown_html, Parser as MarkdownParser}; 5 | use rayon::ThreadPoolBuilder; 6 | use regex::Regex; 7 | use serde::ser::{Serialize, SerializeStruct, Serializer}; 8 | use std::collections::BTreeMap; 9 | use std::env; 10 | use std::env::consts::EXE_EXTENSION; 11 | use std::fs; 12 | use std::path::{Path, PathBuf}; 13 | use std::process::{self, Command, Stdio}; 14 | use std::sync::OnceLock; 15 | 16 | struct Question { 17 | code: String, 18 | prose: Prose, 19 | } 20 | 21 | enum Prose { 22 | Quiz { 23 | difficulty: u8, 24 | answer: String, 25 | hint: String, 26 | explanation: String, 27 | }, 28 | Tombstone, 29 | } 30 | 31 | impl Serialize for Question { 32 | fn serialize(&self, serializer: S) -> Result 33 | where 34 | S: Serializer, 35 | { 36 | match &self.prose { 37 | Prose::Quiz { 38 | difficulty, 39 | answer, 40 | hint, 41 | explanation, 42 | } => { 43 | let mut s = serializer.serialize_struct("Question", 5)?; 44 | s.serialize_field("code", &self.code)?; 45 | s.serialize_field("difficulty", difficulty)?; 46 | s.serialize_field("answer", answer)?; 47 | s.serialize_field("hint", hint)?; 48 | s.serialize_field("explanation", explanation)?; 49 | s.end() 50 | } 51 | Prose::Tombstone => { 52 | let mut s = serializer.serialize_struct("Question", 1)?; 53 | s.serialize_field("code", &self.code)?; 54 | s.end() 55 | } 56 | } 57 | } 58 | } 59 | 60 | pub const MARKDOWN_REGEX: &str = r"(?msx) 61 | \AAnswer:\x20(?Pundefined|error|[0-9]+)\n 62 | Difficulty:\x20(?P1|2|3)\n 63 | (?:Warnings:\x20(?P[a-z_,\x20]+)\n 64 | )?\n 65 | \x23\x20Hint\n 66 | \n 67 | (?P.*) 68 | \n 69 | \x23\x20Explanation\n 70 | \n 71 | (?P.*) 72 | \z 73 | "; 74 | 75 | pub const MARKDOWN_FORMAT: &str = " 76 | Answer: 999 77 | Difficulty: 1|2|3 78 | 79 | # Hint 80 | 81 | 82 | 83 | # Explanation 84 | 85 | 86 | "; 87 | 88 | pub fn main() -> Result<(), Error> { 89 | let mut question_files = Vec::new(); 90 | for entry in fs::read_dir("questions")? { 91 | let entry = entry?; 92 | let path = entry.path(); 93 | if path.to_string_lossy().ends_with(".rs") { 94 | question_files.push(path); 95 | } 96 | } 97 | question_files.sort(); 98 | 99 | let cpus = num_cpus::get(); 100 | let pool = ThreadPoolBuilder::new() 101 | .num_threads(cpus) 102 | .build() 103 | .map_err(Error::Rayon)?; 104 | 105 | let oqueue = Sequencer::stderr(); 106 | let questions = Mutex::new(BTreeMap::new()); 107 | pool.scope(|scope| { 108 | for _ in 0..cpus { 109 | scope.spawn(|_| worker(&oqueue, &question_files, &questions)); 110 | } 111 | }); 112 | 113 | let questions = questions.into_inner(); 114 | if questions.len() < question_files.len() { 115 | // Error already printed. 116 | process::exit(1); 117 | } 118 | 119 | let json_object = serde_json::to_string_pretty(&questions)?; 120 | let javascript = format!("var questions = {};\n", json_object); 121 | fs::write("docs/questions.js", javascript)?; 122 | 123 | Ok(()) 124 | } 125 | 126 | fn worker(oqueue: &Sequencer, files: &[PathBuf], out: &Mutex>) { 127 | loop { 128 | let task = oqueue.begin(); 129 | let Some(rs_path) = files.get(task.index) else { 130 | return; 131 | }; 132 | 133 | writeln!(task, "evaluating {}", rs_path.display()); 134 | 135 | if let Err(err) = work(rs_path, out) { 136 | task.bold_color(Red); 137 | write!(task, "ERROR"); 138 | task.bold(); 139 | writeln!(task, ": {}", err); 140 | } 141 | } 142 | } 143 | 144 | fn work(rs_path: &Path, out: &Mutex>) -> Result<(), Error> { 145 | let code = fs::read_to_string(rs_path)?; 146 | 147 | let md_path = rs_path.with_extension("md"); 148 | let md_content = fs::read_to_string(&md_path)?; 149 | let prose = if md_content.trim() == "tombstone" { 150 | Prose::Tombstone 151 | } else { 152 | let markdown_regex = { 153 | static REGEX: OnceLock = OnceLock::new(); 154 | REGEX.get_or_init(|| Regex::new(MARKDOWN_REGEX).unwrap()) 155 | }; 156 | let Some(markdown_cap) = markdown_regex.captures(&md_content) else { 157 | return Err(Error::MarkdownFormat(md_path)); 158 | }; 159 | 160 | let mut warnings = Vec::new(); 161 | if let Some(regex_match) = markdown_cap.name("warnings") { 162 | for word in regex_match.as_str().split(',') { 163 | warnings.push(word.trim().to_owned()); 164 | } 165 | } 166 | 167 | let answer = markdown_cap["answer"].to_owned(); 168 | let difficulty = markdown_cap["difficulty"].parse().unwrap(); 169 | let hint = render_to_html(&markdown_cap["hint"]); 170 | let explanation = render_to_html(&markdown_cap["explanation"]); 171 | 172 | check_answer(rs_path, &answer, &warnings)?; 173 | 174 | Prose::Quiz { 175 | difficulty, 176 | answer, 177 | hint, 178 | explanation, 179 | } 180 | }; 181 | 182 | let path_regex = { 183 | static REGEX: OnceLock = OnceLock::new(); 184 | REGEX.get_or_init(|| Regex::new(r"questions/(?P[0-9]{3})[a-z0-9-]+\.rs").unwrap()) 185 | }; 186 | let number = match path_regex.captures(rs_path.to_str().unwrap()) { 187 | Some(path_cap) => path_cap["num"] 188 | .parse::() 189 | .expect("three decimal digits"), 190 | None => return Err(Error::FilenameFormat), 191 | }; 192 | 193 | let mut map = out.lock(); 194 | map.insert(number, Question { code, prose }); 195 | Ok(()) 196 | } 197 | 198 | fn render_to_html(markdown: &str) -> String { 199 | let parser = MarkdownParser::new(markdown); 200 | let mut html = String::new(); 201 | markdown_html::push_html(&mut html, parser); 202 | html = html.replace(" Result<(), Error> { 213 | let out_dir = env::temp_dir().join("rust-quiz"); 214 | 215 | let mut cmd = rustc(&out_dir, rs_path); 216 | cmd.arg("--deny=warnings"); 217 | for warning in warnings { 218 | cmd.arg("--allow").arg(warning); 219 | } 220 | 221 | let status = cmd.status().map_err(Error::Rustc)?; 222 | let status = match status.success() { 223 | true => Status::Ok, 224 | false => Status::Err, 225 | }; 226 | 227 | if let Status::Err = status { 228 | if rustc(&out_dir, rs_path) 229 | .arg("--allow=warnings") 230 | .status() 231 | .map_err(Error::Rustc)? 232 | .success() 233 | { 234 | return Err(Error::CompiledWithWarnings); 235 | } 236 | } 237 | 238 | match (expected, status) { 239 | ("undefined", Status::Ok) | ("error", Status::Err) => {} 240 | ("undefined", Status::Err) => return Err(Error::UndefinedShouldCompile), 241 | ("error", Status::Ok) => return Err(Error::ShouldNotCompile), 242 | (_, Status::Err) => return Err(Error::ShouldCompile), 243 | (_, Status::Ok) => run(&out_dir, rs_path, expected)?, 244 | } 245 | 246 | if let Status::Ok = status { 247 | let mut missing_warnings = Vec::new(); 248 | for check_warning in warnings { 249 | let mut cmd = rustc(&out_dir, rs_path); 250 | cmd.arg("--deny=warnings"); 251 | for warning in warnings { 252 | if warning != check_warning { 253 | cmd.arg("--allow").arg(warning); 254 | } 255 | } 256 | if cmd.status().map_err(Error::Rustc)?.success() { 257 | missing_warnings.push(check_warning.clone()); 258 | } 259 | } 260 | if !missing_warnings.is_empty() { 261 | return Err(Error::MissingExpectedWarning(missing_warnings)); 262 | } 263 | } 264 | 265 | Ok(()) 266 | } 267 | 268 | fn rustc(out_dir: &Path, rs_path: &Path) -> Command { 269 | let mut cmd = Command::new("rustc"); 270 | cmd.arg(rs_path) 271 | .arg("--edition=2021") 272 | .arg("--out-dir") 273 | .arg(out_dir) 274 | .stderr(Stdio::null()); 275 | cmd 276 | } 277 | 278 | fn run(out_dir: &Path, rs_path: &Path, expected: &str) -> Result<(), Error> { 279 | let stem = rs_path.file_stem().unwrap(); 280 | let exe = out_dir.join(stem).with_extension(EXE_EXTENSION); 281 | let output = Command::new(exe).output().map_err(Error::Execute)?; 282 | let output = String::from_utf8(output.stdout)?; 283 | 284 | if output == expected { 285 | Ok(()) 286 | } else { 287 | Err(Error::WrongOutput { 288 | expected: expected.to_owned(), 289 | output, 290 | }) 291 | } 292 | } 293 | -------------------------------------------------------------------------------- /src/serve.rs: -------------------------------------------------------------------------------- 1 | use crate::Error; 2 | use futures::future::BoxFuture; 3 | use http::response::Builder as ResponseBuilder; 4 | use http::{header, Request, Response, StatusCode}; 5 | use hyper::body::Incoming; 6 | use hyper::server::conn::http1; 7 | use hyper::service::Service; 8 | use hyper_staticfile::{Body, Static}; 9 | use hyper_util::rt::TokioIo; 10 | use pin_project::pin_project; 11 | use std::future::Future; 12 | use std::io::{self, Write}; 13 | use std::net::{IpAddr, Ipv4Addr, SocketAddr}; 14 | use std::path::Path; 15 | use std::pin::Pin; 16 | use std::task::{Context, Poll}; 17 | use tokio::net::TcpListener; 18 | 19 | const PORT: u16 = 8000; 20 | 21 | #[pin_project(project = MainFutureProj)] 22 | enum MainFuture { 23 | Root, 24 | Static(#[pin] BoxFuture<'static, io::Result>>), 25 | } 26 | 27 | impl Future for MainFuture { 28 | type Output = Result, Error>; 29 | 30 | fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll { 31 | match self.project() { 32 | MainFutureProj::Root => { 33 | let res = ResponseBuilder::new() 34 | .status(StatusCode::MOVED_PERMANENTLY) 35 | .header(header::LOCATION, "/rust-quiz/") 36 | .body(Body::Empty) 37 | .map_err(Error::Http); 38 | Poll::Ready(res) 39 | } 40 | MainFutureProj::Static(future) => future.poll(cx).map_err(Error::Io), 41 | } 42 | } 43 | } 44 | 45 | struct MainService { 46 | staticfile: Static, 47 | } 48 | 49 | impl MainService { 50 | fn new() -> MainService { 51 | MainService { 52 | staticfile: Static::new(Path::new(".")), 53 | } 54 | } 55 | } 56 | 57 | impl Service> for MainService { 58 | type Response = Response; 59 | type Error = Error; 60 | type Future = MainFuture; 61 | 62 | fn call(&self, req: Request) -> Self::Future { 63 | if req.uri().path() == "/" { 64 | MainFuture::Root 65 | } else { 66 | MainFuture::Static(Box::pin(self.staticfile.clone().serve(req))) 67 | } 68 | } 69 | } 70 | 71 | pub async fn main() -> Result<(), Error> { 72 | let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), PORT); 73 | let listener = TcpListener::bind(addr).await?; 74 | 75 | let _ = writeln!( 76 | io::stderr(), 77 | "Quiz server running on http://localhost:{}/ ...", 78 | PORT, 79 | ); 80 | 81 | loop { 82 | let (tcp_stream, _socket_addr) = listener.accept().await?; 83 | let io = TokioIo::new(tcp_stream); 84 | tokio::task::spawn(async move { 85 | if let Err(err) = http1::Builder::new() 86 | .serve_connection(io, MainService::new()) 87 | .await 88 | { 89 | let _ = writeln!(io::stderr(), "{}", err); 90 | } 91 | }); 92 | } 93 | } 94 | --------------------------------------------------------------------------------