From 580687a11eaf9f80264dbc7f009b98b3dedc5021 Mon Sep 17 00:00:00 2001 From: Dan Shields Date: Wed, 5 Apr 2023 23:53:00 -0600 Subject: [PATCH] =?UTF-8?q?=F0=9F=8E=89=20OSS=20Release!=20=F0=9F=9A=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Joshy Orndorff Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com> --- .gitignore | 2 + Cargo.lock | 7 + Cargo.toml | 15 ++ LICENSE | 373 ++++++++++++++++++++++++++++++++++++++ README.md | 77 ++++++++ rustfmt.toml | 7 + src/a_honor_code.rs | 90 +++++++++ src/b_multiple_choice.rs | 367 +++++++++++++++++++++++++++++++++++++ src/d_pattern_matching.rs | 86 +++++++++ src/e_common_traits.rs | 150 +++++++++++++++ src/f_iterators.rs | 137 ++++++++++++++ src/h_advanced_traits.rs | 347 +++++++++++++++++++++++++++++++++++ src/i_extension_traits.rs | 106 +++++++++++ src/k_macros.rs | 107 +++++++++++ src/lib.rs | 16 ++ src/m_builder.rs | 192 ++++++++++++++++++++ 16 files changed, 2079 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 LICENSE create mode 100644 README.md create mode 100644 rustfmt.toml create mode 100644 src/a_honor_code.rs create mode 100644 src/b_multiple_choice.rs create mode 100644 src/d_pattern_matching.rs create mode 100644 src/e_common_traits.rs create mode 100644 src/f_iterators.rs create mode 100644 src/h_advanced_traits.rs create mode 100644 src/i_extension_traits.rs create mode 100644 src/k_macros.rs create mode 100644 src/lib.rs create mode 100644 src/m_builder.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4c7b3d0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +**/target +.vscode diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..a6213fc --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,7 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "pba-qualifier-exam" +version = "1.0.0" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..68b37c0 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "pba-qualifier-exam" +version = "1.0.0" + +# This exam will be graded by rust stable 1.68 using the 2021 edition. +# Solutions may not require newer editions or versions including nightly. +rust-version = "1.68" +edition = "2021" + +# Solutions must not be shared! +publish = false + +[dependencies] +# There should be NO external additions here, per the honor code. +# If you want or need to create a local dependancy, you may do so. \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..a612ad9 --- /dev/null +++ b/LICENSE @@ -0,0 +1,373 @@ +Mozilla Public License Version 2.0 +================================== + +1. Definitions +-------------- + +1.1. "Contributor" + means each individual or legal entity that creates, contributes to + the creation of, or owns Covered Software. + +1.2. "Contributor Version" + means the combination of the Contributions of others (if any) used + by a Contributor and that particular Contributor's Contribution. + +1.3. "Contribution" + means Covered Software of a particular Contributor. + +1.4. "Covered Software" + means Source Code Form to which the initial Contributor has attached + the notice in Exhibit A, the Executable Form of such Source Code + Form, and Modifications of such Source Code Form, in each case + including portions thereof. + +1.5. "Incompatible With Secondary Licenses" + means + + (a) that the initial Contributor has attached the notice described + in Exhibit B to the Covered Software; or + + (b) that the Covered Software was made available under the terms of + version 1.1 or earlier of the License, but not also under the + terms of a Secondary License. + +1.6. "Executable Form" + means any form of the work other than Source Code Form. + +1.7. "Larger Work" + means a work that combines Covered Software with other material, in + a separate file or files, that is not Covered Software. + +1.8. "License" + means this document. + +1.9. "Licensable" + means having the right to grant, to the maximum extent possible, + whether at the time of the initial grant or subsequently, any and + all of the rights conveyed by this License. + +1.10. "Modifications" + means any of the following: + + (a) any file in Source Code Form that results from an addition to, + deletion from, or modification of the contents of Covered + Software; or + + (b) any new file in Source Code Form that contains any Covered + Software. + +1.11. "Patent Claims" of a Contributor + means any patent claim(s), including without limitation, method, + process, and apparatus claims, in any patent Licensable by such + Contributor that would be infringed, but for the grant of the + License, by the making, using, selling, offering for sale, having + made, import, or transfer of either its Contributions or its + Contributor Version. + +1.12. "Secondary License" + means either the GNU General Public License, Version 2.0, the GNU + Lesser General Public License, Version 2.1, the GNU Affero General + Public License, Version 3.0, or any later versions of those + licenses. + +1.13. "Source Code Form" + means the form of the work preferred for making modifications. + +1.14. "You" (or "Your") + means an individual or a legal entity exercising rights under this + License. For legal entities, "You" includes any entity that + controls, is controlled by, or is under common control with You. For + purposes of this definition, "control" means (a) the power, direct + or indirect, to cause the direction or management of such entity, + whether by contract or otherwise, or (b) ownership of more than + fifty percent (50%) of the outstanding shares or beneficial + ownership of such entity. + +2. License Grants and Conditions +-------------------------------- + +2.1. Grants + +Each Contributor hereby grants You a world-wide, royalty-free, +non-exclusive license: + +(a) under intellectual property rights (other than patent or trademark) + Licensable by such Contributor to use, reproduce, make available, + modify, display, perform, distribute, and otherwise exploit its + Contributions, either on an unmodified basis, with Modifications, or + as part of a Larger Work; and + +(b) under Patent Claims of such Contributor to make, use, sell, offer + for sale, have made, import, and otherwise transfer either its + Contributions or its Contributor Version. + +2.2. Effective Date + +The licenses granted in Section 2.1 with respect to any Contribution +become effective for each Contribution on the date the Contributor first +distributes such Contribution. + +2.3. Limitations on Grant Scope + +The licenses granted in this Section 2 are the only rights granted under +this License. No additional rights or licenses will be implied from the +distribution or licensing of Covered Software under this License. +Notwithstanding Section 2.1(b) above, no patent license is granted by a +Contributor: + +(a) for any code that a Contributor has removed from Covered Software; + or + +(b) for infringements caused by: (i) Your and any other third party's + modifications of Covered Software, or (ii) the combination of its + Contributions with other software (except as part of its Contributor + Version); or + +(c) under Patent Claims infringed by Covered Software in the absence of + its Contributions. + +This License does not grant any rights in the trademarks, service marks, +or logos of any Contributor (except as may be necessary to comply with +the notice requirements in Section 3.4). + +2.4. Subsequent Licenses + +No Contributor makes additional grants as a result of Your choice to +distribute the Covered Software under a subsequent version of this +License (see Section 10.2) or under the terms of a Secondary License (if +permitted under the terms of Section 3.3). + +2.5. Representation + +Each Contributor represents that the Contributor believes its +Contributions are its original creation(s) or it has sufficient rights +to grant the rights to its Contributions conveyed by this License. + +2.6. Fair Use + +This License is not intended to limit any rights You have under +applicable copyright doctrines of fair use, fair dealing, or other +equivalents. + +2.7. Conditions + +Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted +in Section 2.1. + +3. Responsibilities +------------------- + +3.1. Distribution of Source Form + +All distribution of Covered Software in Source Code Form, including any +Modifications that You create or to which You contribute, must be under +the terms of this License. You must inform recipients that the Source +Code Form of the Covered Software is governed by the terms of this +License, and how they can obtain a copy of this License. You may not +attempt to alter or restrict the recipients' rights in the Source Code +Form. + +3.2. Distribution of Executable Form + +If You distribute Covered Software in Executable Form then: + +(a) such Covered Software must also be made available in Source Code + Form, as described in Section 3.1, and You must inform recipients of + the Executable Form how they can obtain a copy of such Source Code + Form by reasonable means in a timely manner, at a charge no more + than the cost of distribution to the recipient; and + +(b) You may distribute such Executable Form under the terms of this + License, or sublicense it under different terms, provided that the + license for the Executable Form does not attempt to limit or alter + the recipients' rights in the Source Code Form under this License. + +3.3. Distribution of a Larger Work + +You may create and distribute a Larger Work under terms of Your choice, +provided that You also comply with the requirements of this License for +the Covered Software. If the Larger Work is a combination of Covered +Software with a work governed by one or more Secondary Licenses, and the +Covered Software is not Incompatible With Secondary Licenses, this +License permits You to additionally distribute such Covered Software +under the terms of such Secondary License(s), so that the recipient of +the Larger Work may, at their option, further distribute the Covered +Software under the terms of either this License or such Secondary +License(s). + +3.4. Notices + +You may not remove or alter the substance of any license notices +(including copyright notices, patent notices, disclaimers of warranty, +or limitations of liability) contained within the Source Code Form of +the Covered Software, except that You may alter any license notices to +the extent required to remedy known factual inaccuracies. + +3.5. Application of Additional Terms + +You may choose to offer, and to charge a fee for, warranty, support, +indemnity or liability obligations to one or more recipients of Covered +Software. However, You may do so only on Your own behalf, and not on +behalf of any Contributor. You must make it absolutely clear that any +such warranty, support, indemnity, or liability obligation is offered by +You alone, and You hereby agree to indemnify every Contributor for any +liability incurred by such Contributor as a result of warranty, support, +indemnity or liability terms You offer. You may include additional +disclaimers of warranty and limitations of liability specific to any +jurisdiction. + +4. Inability to Comply Due to Statute or Regulation +--------------------------------------------------- + +If it is impossible for You to comply with any of the terms of this +License with respect to some or all of the Covered Software due to +statute, judicial order, or regulation then You must: (a) comply with +the terms of this License to the maximum extent possible; and (b) +describe the limitations and the code they affect. Such description must +be placed in a text file included with all distributions of the Covered +Software under this License. Except to the extent prohibited by statute +or regulation, such description must be sufficiently detailed for a +recipient of ordinary skill to be able to understand it. + +5. Termination +-------------- + +5.1. The rights granted under this License will terminate automatically +if You fail to comply with any of its terms. However, if You become +compliant, then the rights granted under this License from a particular +Contributor are reinstated (a) provisionally, unless and until such +Contributor explicitly and finally terminates Your grants, and (b) on an +ongoing basis, if such Contributor fails to notify You of the +non-compliance by some reasonable means prior to 60 days after You have +come back into compliance. Moreover, Your grants from a particular +Contributor are reinstated on an ongoing basis if such Contributor +notifies You of the non-compliance by some reasonable means, this is the +first time You have received notice of non-compliance with this License +from such Contributor, and You become compliant prior to 30 days after +Your receipt of the notice. + +5.2. If You initiate litigation against any entity by asserting a patent +infringement claim (excluding declaratory judgment actions, +counter-claims, and cross-claims) alleging that a Contributor Version +directly or indirectly infringes any patent, then the rights granted to +You by any and all Contributors for the Covered Software under Section +2.1 of this License shall terminate. + +5.3. In the event of termination under Sections 5.1 or 5.2 above, all +end user license agreements (excluding distributors and resellers) which +have been validly granted by You or Your distributors under this License +prior to termination shall survive termination. + +************************************************************************ +* * +* 6. Disclaimer of Warranty * +* ------------------------- * +* * +* Covered Software is provided under this License on an "as is" * +* basis, without warranty of any kind, either expressed, implied, or * +* statutory, including, without limitation, warranties that the * +* Covered Software is free of defects, merchantable, fit for a * +* particular purpose or non-infringing. The entire risk as to the * +* quality and performance of the Covered Software is with You. * +* Should any Covered Software prove defective in any respect, You * +* (not any Contributor) assume the cost of any necessary servicing, * +* repair, or correction. This disclaimer of warranty constitutes an * +* essential part of this License. No use of any Covered Software is * +* authorized under this License except under this disclaimer. * +* * +************************************************************************ + +************************************************************************ +* * +* 7. Limitation of Liability * +* -------------------------- * +* * +* Under no circumstances and under no legal theory, whether tort * +* (including negligence), contract, or otherwise, shall any * +* Contributor, or anyone who distributes Covered Software as * +* permitted above, be liable to You for any direct, indirect, * +* special, incidental, or consequential damages of any character * +* including, without limitation, damages for lost profits, loss of * +* goodwill, work stoppage, computer failure or malfunction, or any * +* and all other commercial damages or losses, even if such party * +* shall have been informed of the possibility of such damages. This * +* limitation of liability shall not apply to liability for death or * +* personal injury resulting from such party's negligence to the * +* extent applicable law prohibits such limitation. Some * +* jurisdictions do not allow the exclusion or limitation of * +* incidental or consequential damages, so this exclusion and * +* limitation may not apply to You. * +* * +************************************************************************ + +8. Litigation +------------- + +Any litigation relating to this License may be brought only in the +courts of a jurisdiction where the defendant maintains its principal +place of business and such litigation shall be governed by laws of that +jurisdiction, without reference to its conflict-of-law provisions. +Nothing in this Section shall prevent a party's ability to bring +cross-claims or counter-claims. + +9. Miscellaneous +---------------- + +This License represents the complete agreement concerning the subject +matter hereof. If any provision of this License is held to be +unenforceable, such provision shall be reformed only to the extent +necessary to make it enforceable. Any law or regulation which provides +that the language of a contract shall be construed against the drafter +shall not be used to construe this License against a Contributor. + +10. Versions of the License +--------------------------- + +10.1. New Versions + +Mozilla Foundation is the license steward. Except as provided in Section +10.3, no one other than the license steward has the right to modify or +publish new versions of this License. Each version will be given a +distinguishing version number. + +10.2. Effect of New Versions + +You may distribute the Covered Software under the terms of the version +of the License under which You originally received the Covered Software, +or under the terms of any subsequent version published by the license +steward. + +10.3. Modified Versions + +If you create software not governed by this License, and you want to +create a new license for such software, you may create and use a +modified version of this License if you rename the license and remove +any references to the name of the license steward (except to note that +such modified license differs from this License). + +10.4. Distributing Source Code Form that is Incompatible With Secondary +Licenses + +If You choose to distribute Source Code Form that is Incompatible With +Secondary Licenses under the terms of this version of the License, the +notice described in Exhibit B of this License must be attached. + +Exhibit A - Source Code Form License Notice +------------------------------------------- + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. + +If it is not possible or desirable to put the notice in a particular +file, then You may include the notice in a location (such as a LICENSE +file in a relevant directory) where a recipient would be likely to look +for such a notice. + +You may add additional accurate notices of copyright ownership. + +Exhibit B - "Incompatible With Secondary Licenses" Notice +--------------------------------------------------------- + + This Source Code Form is "Incompatible With Secondary Licenses", as + defined by the Mozilla Public License, v. 2.0. diff --git a/README.md b/README.md new file mode 100644 index 0000000..e52544a --- /dev/null +++ b/README.md @@ -0,0 +1,77 @@ +

Rust Qualifier Exam

+ +An open source learning resource, available to all. + +This exam is maintained by the Polkadot Blockchain Academy, for the benefit of the entire Rust community. +The Academy accepts individuals modestly skilled in Rust, and maintain this exam to test help everyone asses their proficiency being of a level we would consider for the program. + +We encourage everyone to take this exam for fun, to help assess your own Rust understanding, or as part of your [application to the Polkadot Blockchain Academy](#applying-to-the-polkadot-blockchain-academy). + +## Honor Code + +When used as a qualifier for the Academy, this exam is intended to be taken individually without help from other programmers, AIs, or the breadth of the internet. +The first section of the exam is the honor code. +Please read it _carefully and thoughtfully_ before you review or start any work. + +Those not applying to the Academy are free to relax the honor code as you deem fit, however, the **[license and use policy](#license-and-use-policies) forbids publishing solutions** for everyone. +Naturally as an exam, public solutions undermine usefulness of this public good for the whole community. +We humbly ask you respect the intended academic integrity of our community when considering how you interact with the exam in your communities. + +## Time Commitment + +The exam is intended to be quite a challenge for those more novice in Rust. +It is expected to take a minimum of 10-20 hours for the average applicant to complete. +Your time may be shorter or significantly longer than this. + +## Organization + +This exam is structured as a single Rust crate with many modules. +Each module contains some well-commented starter code, and some `todo!()` macros that you should replace with solution code. + +## Tests + +Each module contains a small starter test suite to help you know whether your solutions are on the right track or not. +You can run these tests with standard cargo commands. +The provided tests are mostly failing when you first begin, but they should all pass by the time you are finished. + +```sh +# Run the unit tests +cargo test + +# Run the doc doc tests +cargo test --doc +``` + +The provided tests are **not** a complete test suite. +You are encouraged to write additional tests to ensure that their code works as intended. + +The Academy maintains a larger more thorough test suite privately. +If you are applying, your submission will be assessed with this more thorough test suite. +To be sure that the test suite will always work, **do not change any function or type signatures** unless explicitly permitted to do so. + +The entire exam should be completed using Rust stable toolchain with the specified version in [Cargo.toml](./Cargo.toml) as the Academy test suite will explicitly use this version. + +## Completing the Exam + +Work through the exam by replacing each occurrence of `todo!()` with your solution to the problem. +When all the provided tests pass, plus all the additional tests you wrote are passing, your work is finished. +If you are pushing to any remote servers while solving the exam (as is required for PBA applicants) be sure that the remote servers are not publicly viewable. +You may not publish solutions to the exam, please read the [License and use policies](#license-and-use-policies) for specifics + +## Applying to the Polkadot Blockchain Academy + +The Polkadot Blockchain Academy is a classroom-based educational program covering the conceptual underpinnings and the hands-on application of blockchain technology. +The curriculum covers disciplines such as economics, governance, game theory, cryptography, peer-based and distributed network systems, systems and API design, and much more. +In addition to theoretical modules, students will apply their knowledge and build blockchains and parachains using [Substrate](https://substrate.io). + +To apply to the Polkadot Blockchain Academy, please visit https://polkadot.network/development/academy/ that has information location and dates of the current or next cohort, and a form that will start the process for consideration in the next cohort. +This qualifier exam is just one part of the process and is not, by itself, an application. + +### License and Use Policies + +Mozilla Public License Version 2.0 - See the [LICENSE](./LICENSE) for details. + +In addition to the license, you may not publish materials that reveal, directly or indirectly, in any way, solutions to this exam publicly. +Lastly, you should not share any solutions or privately to prevent peer-collusion and respect the academic integrity of this public good resource of the Academy's community. + +# 🚀 Good luck! 🚀 diff --git a/rustfmt.toml b/rustfmt.toml new file mode 100644 index 0000000..7dd3292 --- /dev/null +++ b/rustfmt.toml @@ -0,0 +1,7 @@ +hard_tabs = true +newline_style = "Unix" + +# Comment formatting requires nightly which is not enforced by CI. +# Nonetheless, we leave the config here so it can be run locally. +comment_width = 100 +wrap_comments = true diff --git a/src/a_honor_code.rs b/src/a_honor_code.rs new file mode 100644 index 0000000..763c0b3 --- /dev/null +++ b/src/a_honor_code.rs @@ -0,0 +1,90 @@ +//! This portion of the exam represents an honor code. By returning `true` from each of these +//! functions you are attesting that you have followed the various rules of the exam. +//! +//! Cheating on this exam will only hurt yourself as you are likely to feel lost and frustrated at +//! the Polkadot Blockchain Academy if you do not have the necessary Rust background to attend. +//! +//! If you are in any doubt of something being allowed or disallowed not, please directly ask the +//! Academy staff for clarification and guidance. + +/// You must work independently on this exam. Seeking help from or otherwise working anyone on your +/// solutions while you are completing it is considered cheating. You are encouraged to ask the +/// Academy staff if something is unclear or you are completely stuck. +pub fn exam_done_independently() -> bool { + // If you have followed this rule, return `true` + todo!() +} + +/// The multiple choice portion of the exam must be completed without accessing the internet, +/// books, or any other resources. +pub fn multiple_choice_closed_book() -> bool { + // If you have followed this rule, return `true` + todo!() +} + +/// The multiple choice portion of this exam has several questions asking for the output of a +/// particular programs. It also has questions asking what change to a program would fix a +/// particular issue. For this reason, you are not permitted to compile or run the code snippets +/// provided in the multiple choice portion. You may only trace them manually in your head or on +/// paper. Pretend that this portion of the exam is being administered on paper, and no computer is +/// available at all. +pub fn multiple_choice_no_run() -> bool { + // If you have followed this rule, return `true` + todo!() +} + +/// The coding portion of the exam allows access to books, and websites such as the Rust book, the +/// standard library reference, and others _explicitly listed_ in the exam prompts themselves. +/// However, you not allowed to look up direct implementation of the specific algorithms we are +/// asking you to write. +/// +/// Examples of allowed searches: +/// - How to swap elements of a slice in Rust +/// - What is the bubble sort algorithm +/// +/// Examples of searches that are cheating: +/// - Bubble sort implementation in Rust +/// - Bubble sort implementation in C/python/etc +/// - How to {do exact thing the problem asks for} in Rust/C/Python/etc +/// +/// If you are in any doubt of something being allowed or disallowed not, please directly ask the +/// Academy staff for clarification and guidance. +pub fn coding_no_copy() -> bool { + // If you have followed this rule, return `true` + todo!() +} + +/// You not allowed to use external dependencies from `crates.io` or elsewhere unless +/// explicitly stated in the problem. +pub fn coding_no_external_deps() -> bool { + // If you have followed this rule, return `true` + todo!() +} + +/// You are not allowed to use AI assisted coding tools like Github Copilot to complete this exam. +pub fn coding_no_ai_helpers() -> bool { + // If you have followed this rule, return `true` + todo!() +} + +#[cfg(test)] +mod tests { + use super::*; + + fn has_honor(f: &dyn Fn() -> bool) { + assert!( + f(), + "Thank you for your honesty in letting us know you have not followed the honor code." + ) + } + + #[test] + fn honor_code_respected() { + has_honor(&multiple_choice_closed_book); + has_honor(&exam_done_independently); + has_honor(&multiple_choice_no_run); + has_honor(&coding_no_copy); + has_honor(&coding_no_external_deps); + has_honor(&coding_no_ai_helpers); + } +} diff --git a/src/b_multiple_choice.rs b/src/b_multiple_choice.rs new file mode 100644 index 0000000..fbead8f --- /dev/null +++ b/src/b_multiple_choice.rs @@ -0,0 +1,367 @@ +//! # Multiple Choice Questions +//! +//! **This portion of the exam is to be completed without accessing the internet, books, the rust +//! compiler, or any other resources.** +//! +//! There are several questions asking for the output of particular programs or asking what change +//! would fix a particular issue. For this reason, you are **not permitted to compile or run the +//! code snippets**. You may only trace them manually in your head or on paper. Pretend that this +//! portion of the exam is being administered on paper, and no computer is available at all. +//! +//! To enable us to auto-grade your answer, you implement the that returns the `char` representing +//! your answer. +//! +//! If you would like a rendered and styled version of these questions simply build an open the Rust +//! Docs for this module, and navigate the the multiple choice question module docs by the command: +//! +//! ```sh +//! cargo doc --open +//! ``` + +/// ## Question 1 +/// +/// Consider this Rust code when answering question 1: +/// +/// ```notest +/// let x = 4; +/// let y = 5; +/// let z = x - y; +/// let y = 3; +/// ``` +/// +/// ### Question 1_A +/// +/// What type is the variable x? +/// +/// - a) usize +/// - b) isize +/// - c) u32 +/// - d) i32 +/// - e) int +pub fn answer_1_a() -> char { + todo!() +} + +/// ### Question 1_B +/// +/// Why can the variable `y` be reassigned in the last line when it was not declared as `mut`? +/// +/// - a) Mutable variables are the default. +/// - b) Even immutable variables can be re-assigned, only `const`s cannot. +/// - c) It is not reassigned, it is shadowed. +/// - d) Because the original `y` value was moved into z` on line 3. +/// - e) Because it is being borrowed. +pub fn answer_1_b() -> char { + todo!() +} + +/// ### Question 1_C +/// +/// What is the value of `z` at the end of the program? +/// +/// - a) 1 +/// - b) -1 +/// - c) 4294967295 (u32 max value) +/// - d) 0 +/// - e) 511 +pub fn answer_1_c() -> char { + todo!() +} + +/// ### Question 1_D +/// +/// Which of the following is not a valid looping construct in Rust? +/// +/// - a) `do {} while x > 0` +/// - b) `for _ in 1..2 {}` +/// - c) `while x > 0 {}` +/// - d) `loop {}` +/// - e) `for _ in vec![1, 2, 3] {}` +pub fn answer_1_d() -> char { + todo!() +} + +/// ## Question 2 +/// +/// Consider this Rust code: +/// +/// ```notest +/// /// Check whether a value is between 5 and 10, and return a user-readable +/// /// string explaining the findings +/// fn value_in_range(n: u8) -> String { +/// if n < 5 { +/// let result = String::from("value is too small"); +/// } +/// else if n > 10 { +/// let result = String::from("value is too large"); +/// } +/// else { +/// let result = String::from("value is just right"); +/// } +/// result +/// } +/// ``` +/// +/// Why does the above program fail to compile? +/// +/// - a) It needs a lifetime parameter. +/// - b) Rust does not support the `else if` syntax. +/// - c) The `u8` type is not appropriate here. +/// - d) It has the wrong return type. +/// - e) The variable `result` is out of scope by the end. +pub fn answer_2() -> char { + todo!() +} + +/// ## Question 3 +/// +/// Consider this Rust code when answering question 3: +/// +/// ```notest +/// fn mystery(n: u32) -> Vec { +/// let mut seq = Vec::new(); +/// for k in 1..n { +/// seq.push(k * k + 3); +/// } +/// seq +/// } +/// ``` +/// +/// ### Question 3_A +/// +/// What value is returned when calling `mystery(6)`? +/// +/// - a) [4, 7, 12, 19, 28] +/// - b) [3, 4, 7, 12, 19] +/// - c) [4, 7, 12, 19, 28, 39] +/// - d) [4, 10, 18, 28, 40] +/// - e) [28, 19, 12, 7, 4] +pub fn answer_3_a() -> char { + todo!() +} + +/// ### Question 3_B +/// +/// Consider changing the range `1..n`. Which of the following would _not_ cause the loop to execute +/// one additional time: +/// +/// - a) `1..=n` +/// - b) `0..n` +/// - c) `1..n+1` +/// - d) `(1..n).step_by(1)` +/// - e) `(1..=n).step_by(1)` +pub fn answer_3_b() -> char { + todo!() +} + +/// ## Question 4 +/// +/// Consider this Rust code when answering question 4: +/// +/// ```notest +/// fn sort(items: &mut [u32]); +/// ``` +/// +/// ### Question 4_A +/// +/// The signature above makes sense for a sorting algorithm that: +/// +/// - a) Returns a copy of the data in a new Vec in sorted order. +/// - b) Returns a copy of the data in a new slice in sorted order. +/// - c) Returns a new subslice of the same data in sorted order +/// - d) Sorts the data "in place" by moving elements around. +/// - e) Sorts the data and removes duplicate items. +pub fn answer_4_a() -> char { + todo!() +} + +/// ### Question 4_B +/// +/// How would this signature need to change to sort arbitrary data? +/// +/// - a) `fn sort(items: &mut [T])` +/// - b) `fn sort(items: &mut [T]);` +/// - c) `fn sort(items: &mut [T]);` +/// - d) `fn sort(items: &mut [T: PartialOrd]);` +/// - e) `fn sort(items: &mut [T: Ord]);` +pub fn answer_4_b() -> char { + todo!() +} + +/// ## Question 5 +/// +/// Which of the following tools is used to install Rust and manage Rust versions and components? +/// +/// - a) cargo +/// - b) rustc +/// - c) rustup +/// - d) clippy +/// - e) nvm +pub fn answer_5() -> char { + todo!() +} + +/// ## Question 6 +/// +/// Consider this Rust code: +/// +/// ```notest +/// struct Fraction { +/// numerator: u32, +/// denominator: u32, +/// } +/// +/// impl From for Fraction { +/// fn from(n: u32) -> Self { +/// Self { +/// numerator: n, +/// denominator: 1, +/// } +/// } +/// } +/// +/// fn main() { +/// let a: u32 = 5; +/// let b: Fraction = a.into(); +/// } +/// ``` +/// +/// Does the above code compile? Why or why not? +/// +/// - a) Yes, because in mathematics, any integer can be turned into a fraction. +/// - b) No, because the denominator should be `n` and the numerator `1`. +/// - c) No, because the `.into()` method comes from the `Into` trait which is not implemented. +/// - d) Yes, because the implementation of `From` implies an implementation of `Into`. +/// - e) Yes, because the implementation of `Into` implies an implementation of `From`. +pub fn answer_6() -> char { + todo!() +} + +/// ## Question 7 +/// +/// Consider this Rust code: +/// +/// ```notest +/// use std::num::ParseIntError; +/// +/// enum OutOfRangeError { +/// TooLarge, +/// TooSmall, +/// NotEvenANumber, +/// } +/// +/// impl From for OutOfRangeError { +/// fn from(_e: ParseIntError) -> Self { +/// Self::NotEvenANumber +/// } +/// } +/// +/// fn string_to_int_in_range(s: String) -> Result { +/// // Given: The u32::from_str_radix function returns Result +/// let n: u32 = u32::from_str_radix(&s,10)?; +/// +/// match n { +/// n if n < 5 => Err(OutOfRangeError::TooSmall), +/// n if n > 100 => Err(OutOfRangeError::TooLarge), +/// n => Ok(n), +/// } +/// } +/// ``` +/// +/// Does this code compile? Why or why not? +/// +/// - a) No, because you cannot use `if` as part of a pattern. +/// - b) No, because `from_str_radix` does not return `Err(OutOfRangeError)` in any case, and that +/// is the error type required by `string_to_int_in_range`. +/// - c) Yes, because any error type can be converted to any other error type by the `?` operator. +/// - d) No, because the potential `ParseIntError` is never handled. +/// - e) Yes, because the `?` operator implicitly performs a `.into()` before returning the error. +pub fn answer_7() -> char { + todo!() +} + +/// This function is not graded. It is just for collecting feedback. +/// On a scale from 0 - 255, with zero being extremely easy and 255 being extremely hard, +/// how hard did you find this section of the exam. +pub fn how_hard_was_this_section() -> u8 { + todo!() +} + +/// This function is not graded. It is just for collecting feedback. +/// How much time (in hours) did you spend on this section of the exam? +pub fn how_many_hours_did_you_spend_on_this_section() -> u8 { + todo!() +} + +#[cfg(test)] +mod tests { + use super::*; + + fn sanity_check(f: &dyn Fn() -> char) { + assert!( + "abcde".contains(f()), + "{}", + "You have not returned an answer a, b, c, d, or e." + ) + } + + #[test] + fn answer_1_a_sanity_check() { + sanity_check(&answer_1_a) + } + + #[test] + fn answer_1_b_sanity_check() { + sanity_check(&answer_1_b) + } + + #[test] + fn answer_1_c_sanity_check() { + sanity_check(&answer_1_c) + } + + #[test] + fn answer_1_d_sanity_check() { + sanity_check(&answer_1_d) + } + + #[test] + fn answer_2_sanity_check() { + sanity_check(&answer_2) + } + + #[test] + fn answer_3_a_sanity_check() { + sanity_check(&answer_3_a) + } + + #[test] + fn answer_3_b_sanity_check() { + sanity_check(&answer_3_b) + } + + #[test] + fn answer_4_a_sanity_check() { + sanity_check(&answer_4_a) + } + + #[test] + fn answer_4_b_sanity_check() { + sanity_check(&answer_4_b) + } + + #[test] + fn answer_5_sanity_check() { + sanity_check(&answer_5) + } + + #[test] + fn answer_6_sanity_check() { + sanity_check(&answer_6) + } + + #[test] + fn answer_7_sanity_check() { + sanity_check(&answer_7) + } +} diff --git a/src/d_pattern_matching.rs b/src/d_pattern_matching.rs new file mode 100644 index 0000000..32a9d8e --- /dev/null +++ b/src/d_pattern_matching.rs @@ -0,0 +1,86 @@ +//! Complete the following functions using the pattern matching syntax. That includes the `match` +//! statement of the `matches!()` macro, if you feel like having an "1-liner". +//! +//! You can try and write them imperatively at first as well, but at the end of the day, we want you +//! to write them using the `match!` or `matches!`. + +/// Returns true if the last two strings in the vector start with `PBA`. +pub fn match_1(input: Vec) -> bool { + todo!(); +} + +/// Returns true if the first and last string in the vector start with `PBA`. +pub fn match_2(input: Vec) -> bool { + todo!(); +} + +/// Returns true if the first item in `input` is true. +pub fn match_3(input: (bool, bool, bool)) -> bool { + todo!(); +} + +/// Returns true if the input is `Ok(x)` of some even `x`. +pub fn match_4(input: Result) -> bool { + todo!(); +} + +/// This function is not graded. It is just for collecting feedback. +/// On a scale from 0 - 255, with zero being extremely easy and 255 being extremely hard, +/// how hard did you find this section of the exam. +pub fn how_hard_was_this_section() -> u8 { + todo!() +} + +/// This function is not graded. It is just for collecting feedback. +/// How much time (in hours) did you spend on this section of the exam? +pub fn how_many_hours_did_you_spend_on_this_section() -> u8 { + todo!() +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_match_1_true() { + let strs = vec![ + "Hello".to_string(), + "World".to_string(), + "PBA".to_string(), + "PBASDFGR".to_string(), + ]; + assert!(match_1(strs)) + } + + #[test] + fn test_match_1_false() { + let strs = vec!["Hello".to_string(), "World".to_string(), "PBA".to_string()]; + assert!(!match_1(strs)) + } + + #[test] + fn test_match_2() { + let strs = vec![ + "PBAHello".to_string(), + "World".to_string(), + "PBA".to_string(), + "PBASDFGR".to_string(), + ]; + assert!(match_2(strs)) + } + + #[test] + fn test_match_3() { + assert!(match_3((true, false, true))) + } + + #[test] + fn test_match_4_true() { + assert!(match_4(Ok(6))) + } + + #[test] + fn test_match_4_false() { + assert!(!match_4(Err("bogus"))) + } +} diff --git a/src/e_common_traits.rs b/src/e_common_traits.rs new file mode 100644 index 0000000..342a8f3 --- /dev/null +++ b/src/e_common_traits.rs @@ -0,0 +1,150 @@ +//! This portion will test your familiarity with some of Rust's common traits and your ability to +//! implement those traits in interesting ways. You need to remove all of the `todo!()`s. Most of +//! them will need to be replaced by some code, but some may simply be deleted. + +// NOTE: You will need to `use` something from the standard library to implement `Ord` and +// `PartialOrd` here. + +/// A record of an employee at a particular company +#[derive(Debug)] +pub struct Employee { + /// The name the person likes to be called. Doesn't have to be their "passport name" + pub name: String, + /// Amount of experience (in months) the person has working at this company + pub experience: u32, + /// Hourly wage paid to this employee + pub wage: u32, + /// Unique identifier for this employee + pub uid: u32, +} + +// We want to consider two employee instances equal iff they have the same `uid`. + +impl PartialEq for Employee { + fn eq(&self, other: &Self) -> bool { + todo!("complete the implementation"); + } +} +impl Eq for Employee {} + +// We want to sort employees. First and foremost, employees are equal if they have the same +// `uid`, as explained above. For employees who are not equal, we sort by the value they +// bring to the company. Value is defined as the quotient of the experience they've acquired +// at the company divided by their wage. Use integer division for the purpose of this calculation. + +impl PartialOrd for Employee { + fn partial_cmp(&self, other: &Self) -> Option { + todo!("complete the implementation"); + } +} + +impl Ord for Employee { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + todo!("complete the implementation"); + } +} + +// We want to parse employee information from a string data +// The string data should be comma separated. Here are a few examples: +// +// * "Billy, 4, 5, 345" - Billy has been working here for 4 months and earns 5 token per hour. She +// is employee #345 +// * "Jose, 12, 6, 1" - Jose has been working here for 1 year (12 months) and earns 6 +// tokens per hour. He is employee #1 +// +// Any strings with the wrong number of commas or numbers too big for `u32` should return `Err(_)` +// where the error message may be anything. + +impl TryFrom for Employee { + type Error = &'static str; + + fn try_from(value: String) -> Result { + todo!("complete the implementation"); + } +} + +// We also want to convert employees back into strings in the same format as above. +impl From for String { + fn from(e: Employee) -> Self { + todo!("complete the implementation"); + } +} + +/// This function is not graded. It is just for collecting feedback. +/// On a scale from 0 - 255, with zero being extremely easy and 255 being extremely hard, +/// how hard did you find this section of the exam. +pub fn how_hard_was_this_section() -> u8 { + todo!() +} + +/// This function is not graded. It is just for collecting feedback. +/// How much time (in hours) did you spend on this section of the exam? +pub fn how_many_hours_did_you_spend_on_this_section() -> u8 { + todo!() +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn employee_from_string_success() { + let s = String::from("Billy, 4, 5, 345"); + let billy = Employee { + name: String::from("Billy"), + experience: 4, + wage: 5, + uid: 345, + }; + + assert_eq!(billy, s.try_into().unwrap()) + } + + #[test] + fn employee_to_string_success() { + let billy = Employee { + name: String::from("Billy"), + experience: 4, + wage: 5, + uid: 345, + }; + + assert_eq!(String::from("Billy, 4, 5, 345"), String::from(billy)) + } + + #[test] + fn employee_ord() { + let billy = Employee { + name: String::from("Billy"), + experience: 4, + wage: 5, + uid: 345, + }; + let susie = Employee { + name: String::from("Susie"), + experience: 5, + wage: 5, + uid: 347, + }; + + assert!(susie > billy); + } + + #[test] + fn employee_neq() { + let billy = Employee { + name: String::from("Billy"), + experience: 4, + wage: 5, + uid: 345, + }; + let susie = Employee { + name: String::from("Susie"), + experience: 5, + wage: 5, + uid: 347, + }; + + assert!(susie != billy); + } +} diff --git a/src/f_iterators.rs b/src/f_iterators.rs new file mode 100644 index 0000000..6d53b69 --- /dev/null +++ b/src/f_iterators.rs @@ -0,0 +1,137 @@ +//! This portion of the exam tests your abilities to work with iterators and their functional-style +//! methods. +//! +//! Throughout this portion of the test, you may refer to +//! and other docs about iterators. You may NOT look up specific implementations for these problems +//! in Rust or any other Functional language. +//! +//! If you find that you simply cannot write these methods in the functional style using iterator +//! methods, writing them in the imperative style with loops will still earn partial credit. + +/// This function takes an iterator of u32 values, squares each value, and returns the sum of the +/// squares. You may assume that no individual square, nor the entire sum, overflows the u32 type. +pub fn sum_of_squares(vals: impl Iterator) -> u32 { + todo!() +} + +/// This function takes an iterator of i32 values, calculates the absolute value of each, and throws +/// away any values that are greater than 100. The remaining positive values are returned as an +/// iterator of u32s. +pub fn bounded_absolute_values(vals: impl Iterator) -> impl Iterator { + // You should remove the following line (and this comment). It is just there because the + // compiler doesn't allow todo!() when the return type is impl Trait + Vec::new().into_iter() +} + +// We allow `unused_mut` only so that there is no build warning on the starter code. +// You should remove this line once you have completed the following function + +/// This function takes an iterator of u32 values. The first value in the iterator, call it n, is +/// special: it represents the maximum count of the resultant iterator. Once n is known, create an +/// iterator that yields the first n even values from the remainder of the input iterator. +/// +/// If the input iterator is empty, return None +/// If there are fewer than n even values left in the input, return as many as possible +#[allow(unused_mut)] +pub fn first_n_even(mut vals: impl Iterator) -> Option> { + // You should remove the following line (and this comment). It is just there because the + // compiler doesn't allow todo!() when the return type is impl Trait + Some(Vec::new().into_iter()) +} + +/// Return an "infinite" iterator that yields the squares of the whole numbers. +/// For example, the first few values should be 0, 1, 4, 9, 16, 25, ... +/// +/// The iterator should be bounded only by the u32 type, not by your code +pub fn square_whole_numbers() -> impl Iterator { + // You should remove the following line (and this comment). It is just there because the + // compiler doesn't allow todo!() when the return type is impl Trait + Vec::new().into_iter() +} + +/// An iterator that generates the Fibonacci sequence. +#[derive(Default)] +pub struct Fibonacci { + /// The most recent value this iterator has yielded + prev: Option, + /// The second most recent value that this iterator has yielded + prev_prev: Option, +} + +impl Iterator for Fibonacci { + type Item = u32; + + fn next(&mut self) -> Option { + todo!() + } +} + +/// This function is not graded. It is just for collecting feedback. +/// On a scale from 0 - 255, with zero being extremely easy and 255 being extremely hard, +/// how hard did you find this section of the exam. +pub fn how_hard_was_this_section() -> u8 { + todo!() +} + +/// This function is not graded. It is just for collecting feedback. +/// How much time (in hours) did you spend on this section of the exam? +pub fn how_many_hours_did_you_spend_on_this_section() -> u8 { + todo!() +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn sum_of_squares_1() { + let initial = [1u32, 2, 3].into_iter(); + + assert_eq!(14, sum_of_squares(initial)); + } + + #[test] + fn bounded_absolute_values_1() { + let initial = [1, 5, -5, 101, -200, 9, 0].into_iter(); + let expected = vec![1u32, 5, 5, 9, 0]; + + assert_eq!( + expected, + bounded_absolute_values(initial).collect::>() + ); + } + + #[test] + fn first_n_even_1() { + let initial = [3u32, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10].into_iter(); + let expected = vec![2u32, 4, 6]; + + assert_eq!(expected, first_n_even(initial).unwrap().collect::>()); + } + + #[test] + fn first_n_even_2() { + let initial = [3u32, 1, 3, 5, 7, 9].into_iter(); + + assert_eq!( + Vec::::new(), + first_n_even(initial).unwrap().collect::>() + ); + } + + #[test] + fn square_whole_numbers_1() { + assert_eq!( + vec![0, 1, 4, 9, 16, 25], + square_whole_numbers().take(6).collect::>() + ); + } + + #[test] + fn fibonacci_1() { + let fib = Fibonacci::default(); + let expected = vec![0u32, 1, 1, 2, 3, 5, 8]; + + assert_eq!(expected, fib.take(7).collect::>()); + } +} diff --git a/src/h_advanced_traits.rs b/src/h_advanced_traits.rs new file mode 100644 index 0000000..ab29cdf --- /dev/null +++ b/src/h_advanced_traits.rs @@ -0,0 +1,347 @@ +// First, we are going to introduce some units of energy. For whatever reason, we prefer BTU above +// Joules and Calories, but we want to support all 3 of these in this module. Double check the +// conversion methods, and make sure you fully understand them. + +use std::marker::PhantomData; + +// You may uncomment and use the following import if you need it. You may also read its +// documentation at https://doc.rust-lang.org/std/cell/struct.RefCell +// use std::cell::RefCell; + +#[derive(Eq, PartialEq, Debug, Clone, Copy)] +pub struct Joule(pub u32); +#[derive(Eq, PartialEq, Debug, Clone, Copy)] +pub struct Calorie(pub u32); + +pub type BTU = u32; + +impl From for BTU { + fn from(j: Joule) -> Self { + j.0 / 1055 + } +} + +impl From for Joule { + fn from(b: BTU) -> Self { + Self(b * 1055) + } +} + +impl From for BTU { + fn from(c: Calorie) -> Self { + c.0 / 251 + } +} + +impl From for Calorie { + fn from(b: BTU) -> Self { + Calorie(b * 251) + } +} + +// Now, we start defining some types of fuel. + +/// A technology for storing energy for later consumption. +pub trait Fuel { + /// The output unit of the energy density. + /// + /// Think about this: why did we chose this to be an associated type rather than a generic? + type Output: Into + From; + + /// The amount of energy contained in a single unit of fuel. + fn energy_density() -> Self::Output; +} + +pub struct Diesel; +impl Fuel for Diesel { + type Output = Joule; + fn energy_density() -> Self::Output { + todo!("100 BTU") + } +} + +pub struct LithiumBattery; +impl Fuel for LithiumBattery { + type Output = Calorie; + fn energy_density() -> Self::Output { + todo!("200 BTU") + } +} + +pub struct Uranium; +impl Fuel for Uranium { + type Output = Joule; + fn energy_density() -> Self::Output { + todo!("1000 BTU") + } +} + +/// A container for any fuel type. +pub struct FuelContainer { + /// The amount of fuel. + amount: u32, + /// NOTE: Fuel doesn't really have any methods that require `&self` on it, + /// so any information that we can get, we can get from `F` as **TYPE**, we don't really need + /// to store an instance of `F`, like `fuel: F` as a struct field. But to satisfy the compiler, + /// we must use `F` somewhere. + /// Thus, this is the perfect use case of `PhantomData`. + _marker: PhantomData, +} + +impl FuelContainer { + pub fn new(amount: u32) -> Self { + Self { + amount, + _marker: Default::default(), + } + } +} + +/// Something that can provide energy from a given `F` fuel type, like a power-plant. +pub trait ProvideEnergy { + /// Consume the fuel container and return the created energy, based on the power density of the + /// fuel and potentially other factors. + /// + /// Some fuel providers might have some kind of decay or inefficiency, which should be reflected + /// here. Otherwise, [ProvideEnergy::provide_energy_with_efficiency] or + /// [ProvideEnergy::provide_energy_ideal] might be good enough. + /// + /// Not all `ProvideEnergy` implementations need to have internal state. Therefore, this + /// interface accepts `&self`, not `&mut self`. You might need to use special language features + /// to overcome this. + fn provide_energy(&self, f: FuelContainer) -> ::Output; + + /// Convert the amount of fuel in `f` with an exact efficiency of `e`. + /// + /// NOTE: all efficiencies are interpreted as u8 values that can be at most 100, and represent a + /// percent. + /// + /// This method must be provided as it will be the same in all implementations. + fn provide_energy_with_efficiency(&self, f: FuelContainer, e: u8) -> ::Output { + todo!(); + } + + /// Same as [`ProvideEnergy::provide_energy_with_efficiency`], but with an efficiency of 100. + /// + /// This method must be provided as it will be the same in all implementations. + fn provide_energy_ideal(&self, f: FuelContainer) -> ::Output { + todo!(); + } +} + +/// A nuclear reactor that can only consume `Uranium` and provide energy with 99% efficiency. +pub struct NuclearReactor; +impl ProvideEnergy for NuclearReactor { + fn provide_energy(&self, f: FuelContainer) -> ::Output { + todo!("complete the implementation; note that you might need to change the trait bounds and generics of the `impl` line"); + } +} + +/// A combustion engine that can only consume `Diesel`. +/// +/// The `DECAY` const must be interpreted as such: per every `DECAY` times `provide_energy` is +/// called on an instance of this type, the efficiency should reduce by one. The initial efficiency +/// must be configurable with a `fn new(efficiency: u8) -> Self`. +pub struct InternalCombustion(/* Fill the fields as needed */); + +impl InternalCombustion { + pub fn new(efficiency: u8) -> Self { + todo!() + } +} + +impl ProvideEnergy for InternalCombustion { + fn provide_energy(&self, f: FuelContainer) -> ::Output { + todo!("complete the implementation; note that you might need to change the trait bounds and generics of the `impl` line"); + } +} + +/// A hypothetical device that can, unlike the `InternalCombustion`, consume **any fuel** that's of +/// type `trait Fuel`. It can provide a fixed efficiency regardless of fuel type. As before, +/// EFFICIENCY is a u8 whose value should not exceed 100 and is interpreted as a percent. +pub struct OmniGenerator; + +// NOTE: implement `ProvideEnergy` for `OmniGenerator` using only one `impl` block. +impl ProvideEnergy for OmniGenerator { + fn provide_energy(&self, f: FuelContainer) -> ::Output { + todo!("complete the implementation; note that you might need to change the trait bounds and generics of the `impl` line"); + } +} + +/// A type that can wrap two different fuel types and mix them together. +/// +/// The energy density of the new fuel type is the average of the two given, once converted to BTU. +/// The output unit should also be BTU. +/// +/// This can represent a new fuel type, thus it must implement `Fuel`. +pub struct Mixed(PhantomData<(F1, F2)>); + +impl Fuel for Mixed { + type Output = BTU; + + fn energy_density() -> Self::Output { + todo!("complete the implementation; note that you might need to change the trait bounds and generics of the `impl` line"); + } +} + +// Now think about how you can make the mixer configurable, such that it would produce a new fuel +// with an energy density that is more influences by one type than the other. +// +// For example, you have a mixer of F1, F2, and some coefficient C1, where the energy density of the +// mixture is `F1 * C1 + F2 * (1 - C1) )` where `C1` is a ratio (which you have to represent again +// with a u8 percent). +// +// The main trick is to overcome the fact that `fn energy_density` does not take in a `self`, so the +// coefficients need to be incorporated in some other way (you've already seen examples of that in +// this file ;)). +pub struct CustomMixed(PhantomData<(F1, F2)>); +impl Fuel for CustomMixed { + type Output = BTU; + + fn energy_density() -> Self::Output { + todo!("complete the implementation; note that you might need to change the trait bounds and generics of the `impl` line"); + } +} + +// Now, any of our existing energy providers can be used with a mix fuel. + +/// A function that returns the energy produced by the `OmniGenerator` with efficiency of 80%, when +/// the fuel type is an even a mix of `Diesel` as `LithiumBattery`; +pub fn omni_80_energy(amount: u32) -> BTU { + todo!(); +} + +// Finally, let's consider marker traits, and some trait bounds. + +/// Some traits are just markers. They don't bring any additional functionality anything, other than +/// marking a type with some trait. +pub trait IsRenewable {} +impl IsRenewable for LithiumBattery {} + +/// Define the following struct such that it only provides energy if the fuel is `IsRenewable`. +/// +/// It has perfect efficiency. +pub struct GreenEngine(pub PhantomData); +impl ProvideEnergy for GreenEngine { + fn provide_energy(&self, f: FuelContainer) -> ::Output { + todo!("complete the implementation; note that you might need to change the trait bounds and generics of the `impl` line"); + } +} + +/// Define the following struct such that it only provides energy if the fuel's output type is +/// `BTU`. +/// +/// It has perfect efficiency. +pub struct BritishEngine(pub PhantomData); +impl ProvideEnergy for BritishEngine { + fn provide_energy(&self, f: FuelContainer) -> ::Output { + todo!("complete the implementation; note that you might need to change the trait bounds and generics of the `impl` line"); + } +} + +// Congratulations! you have finished the advance trait section. +// +// Disclaimer: the types and traits that you are asked to implement in this module are by no means +// designed to be sensible. Instead, they are chosen to represent a typical, often difficult, +// pattern. Some are intentionally slightly convoluted to challenge you :). I am sure if we actually +// wanted to design a fuel system, we would do better. + +/// This function is not graded. It is just for collecting feedback. +/// On a scale from 0 - 255, with zero being extremely easy and 255 being extremely hard, +/// how hard did you find this section of the exam. +pub fn how_hard_was_this_section() -> u8 { + todo!() +} + +/// This function is not graded. It is just for collecting feedback. +/// How much time (in hours) did you spend on this section of the exam? +pub fn how_many_hours_did_you_spend_on_this_section() -> u8 { + todo!() +} + +#[cfg(test)] +mod tests { + use super::*; + + trait ToBTU { + fn to_btu(self) -> BTU; + } + + impl> ToBTU for T { + fn to_btu(self) -> BTU { + self.into() + } + } + + #[test] + fn nuclear() { + let nr = NuclearReactor; + assert_eq!( + nr.provide_energy(FuelContainer::::new(10)) + .to_btu(), + 9900 + ); + assert_eq!( + nr.provide_energy(FuelContainer::::new(10)) + .to_btu(), + 9900 + ); + } + + #[test] + fn ic_1() { + let ic = InternalCombustion::<3>::new(120); + assert_eq!( + ic.provide_energy(FuelContainer::::new(10)).to_btu(), + 1000 + ); + assert_eq!( + ic.provide_energy(FuelContainer::::new(10)).to_btu(), + 1000 + ); + assert_eq!( + ic.provide_energy(FuelContainer::::new(10)).to_btu(), + 1000 + ); + assert_eq!( + ic.provide_energy(FuelContainer::::new(10)).to_btu(), + 990 + ); + } + + #[test] + fn omni_1() { + let og = OmniGenerator::<100>; + assert_eq!( + og.provide_energy(FuelContainer::::new(10)) + .to_btu(), + 10000 + ); + assert_eq!( + og.provide_energy(FuelContainer::::new(10)).to_btu(), + 1000 + ); + assert_eq!( + og.provide_energy(FuelContainer::::new(10)) + .to_btu(), + 2000 + ); + } + + #[test] + fn mixed_1() { + assert_eq!( + Mixed::::energy_density().to_btu(), + 150 + ); + } + + #[test] + fn custom_mixed_1() { + // custom with 50 is the same as Mixed. + assert_eq!( + CustomMixed::<50, Diesel, LithiumBattery>::energy_density().to_btu(), + Mixed::::energy_density() + ); + } +} diff --git a/src/i_extension_traits.rs b/src/i_extension_traits.rs new file mode 100644 index 0000000..a76f5ca --- /dev/null +++ b/src/i_extension_traits.rs @@ -0,0 +1,106 @@ +// Imagine you have an outcome enum like this. + +#[derive(Clone)] +pub enum Outcome { + Ok, + SomethingWentWrong, + IDontKnow, +} + +// A function takes some arbitrary input that's a collection of `T`, and processes each item +// individually. Each process can be an `Outcome`. We return `Vec`. + +pub fn process_stuff(input: impl Iterator) -> Vec { + unimplemented!("You are not expected to implement this function"); +} + +// What we want to achieve is a quick way (in terms of lines of code) to scan the output and +// determine how many were okay, how many were error, etc. +// +// A boring solution follows 🫣: + +pub fn ok_count(outcomes: Vec) -> usize { + todo!(); +} +pub fn something_went_wrong_count(outcomes: Vec) -> usize { + todo!(); +} +pub fn i_dont_know_count(outcomes: Vec) -> usize { + todo!(); +} + +// This is quite lame. We want to be able to call these methods directly on the `Vec`. But +// how do we do this? We can't add a function to type `Vec`. This type is part of the standard +// library! +// +// Correct, but we can define a trait in the current module, and implement that for `Vec<_>`. +// +// This is a very common approach, and is called an "extension trait". + +pub trait OutcomeCount { + fn ok_count(&self) -> usize; + fn something_went_wrong_count(&self) -> usize; + fn i_dont_know_count(&self) -> usize; +} + +// First, implement this trait. + +impl OutcomeCount for Vec { + fn ok_count(&self) -> usize { + todo!(); + } + fn i_dont_know_count(&self) -> usize { + todo!(); + } + fn something_went_wrong_count(&self) -> usize { + todo!(); + } +} + +// Now we can call these functions directly on `Vec`. + +// But all of that is a lot of boilerplate. Wouldn't it be nice to have a `derive` macro that +// exactly does this, on any enum? +// +// So, for any `enum Foo { X, Y, .. }`, `#[derive(CountOf)]` would generate a trait `CountOfFoo`, +// with functions named `fn x_count`, `fn y_count` etc. Finally, it would implement `CountOfFoo` for +// `Vec`. +// +// And heck, you could then easily implement it for other collections of `Foo`, such as +// `HashMap<_, Foo>` etc. + +/// This function is not graded. It is just for collecting feedback. +/// On a scale from 0 - 255, with zero being extremely easy and 255 being extremely hard, +/// how hard did you find this section of the exam. +pub fn how_hard_was_this_section() -> u8 { + todo!() +} + +/// This function is not graded. It is just for collecting feedback. +/// How much time (in hours) did you spend on this section of the exam? +pub fn how_many_hours_did_you_spend_on_this_section() -> u8 { + todo!() +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn simple_functions() { + let x = vec![Outcome::Ok, Outcome::Ok, Outcome::IDontKnow]; + + assert_eq!(ok_count(x.clone()), 2); + assert_eq!(i_dont_know_count(x.clone()), 1); + assert_eq!(something_went_wrong_count(x), 0); + } + + #[test] + fn extension_trait() { + let x = vec![Outcome::Ok, Outcome::Ok, Outcome::IDontKnow]; + + assert_eq!(x.ok_count(), 2); + assert_eq!(x.i_dont_know_count(), 1); + assert_eq!(x.something_went_wrong_count(), 0); + } +} diff --git a/src/k_macros.rs b/src/k_macros.rs new file mode 100644 index 0000000..b0daad2 --- /dev/null +++ b/src/k_macros.rs @@ -0,0 +1,107 @@ +//! Here we test your ability to write macros. +//! +//! Make sure these macros are importable from the root of your crate, and usable in an external +//! crate. + +// A common Rust macro is `vec![...]` used for creating vectors of literal values. Without this +// macro, one would need to create an empty vector, and push each item to it individually. +// +// Your task here is to create an analogous macro for creating hashmaps pre-populated with literal +// values. The macro should be called like follows: +// +// let map1: HashMap = map![1 =>2, 3 => 4, 5 => 6)]; +#[macro_export] +macro_rules! map { + ( $($todo:tt)* ) => { + Default::default() + }; +} + +/// Next, write a macro that implements a `get` function on a type. +/// +/// Observe the following `trait Get`. It is merely a way to convey a value of type `T` via a `type` +/// (ie) a struct that implements `Get`. An example of manually building one of such structs is what +/// you see in `struct Seven`. It works, but it is verbose. We want a macro that would automatically +/// generate this implementation for us, as such: +/// +/// ``` +/// use pba_entrance_exam::impl_get; +/// impl_get! { +/// // implements `Get` for `struct Six` +/// Six: u32 = 6; +/// // implements `Get` for `struct FortyTwo` +/// FortyTwo: u16 = 42; +/// } +/// ``` +/// +/// For now, your macro could only support literals as the right hand side of `=`. But you can think +/// about the ramifications of allowing arbitrary expressions as well. +/// +/// An important detail that you need to be aware of is that this macro could be called from outside +/// of this file as well. This means that you need to reference `trait Get` with its full path, +/// using `$crate`. Read more: `https://doc.rust-lang.org/reference/macros-by-example.html#hygiene` + +pub trait Get { + fn get() -> T; +} + +struct Seven; +impl Get for Seven { + fn get() -> u32 { + 7 + } +} + +// note that you should first-thing change `$($todo:tt)*`, this simply means 'accept anything'. + +#[macro_export] +macro_rules! impl_get { + ( $($todo:tt)* ) => {}; +} + +/// This function is not graded. It is just for collecting feedback. +/// On a scale from 0 - 255, with zero being extremely easy and 255 being extremely hard, +/// how hard did you find this section of the exam. +pub fn how_hard_was_this_section() -> u8 { + todo!() +} + +/// This function is not graded. It is just for collecting feedback. +/// How much time (in hours) did you spend on this section of the exam? +pub fn how_many_hours_did_you_spend_on_this_section() -> u8 { + todo!() +} + +#[cfg(test)] +mod tests { + use std::collections::HashMap; + + #[test] + fn map() { + let macro_generated: HashMap = map![1 => 2, 3 => 4, 5 => 6]; + let mut expected = HashMap::::new(); + expected.insert(1, 2); + expected.insert(3, 4); + expected.insert(5, 6); + + assert_eq!(macro_generated, expected); + } + + #[test] + fn impl_get() { + impl_get!( + // should generate `struct Foo` that implements `Get` + Foo: u32 = 10; + // should generate `pub struct Bar` that implements `Get` + pub Bar: u32 = 42; + // should generate `pub struct Baz` that has implements `Get`. + pub Baz: u16 = 21; + ); + + // you should be able to make these work. + // assert_eq!(Foo::get(), 10); + // assert_eq!(Bar::get(), 42); + // const CONST: u32 = Baz::get(); + // assert_eq!(CONST, 42); + } +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..6bcf051 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,16 @@ +// NOTE: We allow dead code and unused variables and macros throughout the exam because they appear +// in many places in the starter code. You should remove this when you think you are done so that +// the CI can help you find potential mistakes. +#![allow(dead_code)] +#![allow(unused_variables)] +#![allow(unused_macros)] + +pub mod a_honor_code; +pub mod b_multiple_choice; +pub mod d_pattern_matching; +pub mod e_common_traits; +pub mod f_iterators; +pub mod h_advanced_traits; +pub mod i_extension_traits; +pub mod k_macros; +pub mod m_builder; diff --git a/src/m_builder.rs b/src/m_builder.rs new file mode 100644 index 0000000..0479a41 --- /dev/null +++ b/src/m_builder.rs @@ -0,0 +1,192 @@ +//! In this module, we will explore the "builder" and "type-state" patterns in Rust, both of which +//! are extensively used in Substrate. +//! +//! There are ample resources about both of these online, so we will not go into too much detail +//! here. Here's one of the favorites of one of the instructors ;): +//! +//! +//! We will reuse the types from `e_common_traits` module and create a builder for the [`Employee`] +//! type. + +use crate::e_common_traits::Employee; + +/// First, let's build a naive builder. This builder should allow you to build an [`Employee`], +/// where the `name` and `uid` must be initialized, but the `experience` and `wage` can be left at +/// their default values, 0. +/// +/// The final `fn build` return `Err(())` if either of `name` or `id` are not specified. See the +/// example section below. +/// +/// > PS. Did you now know that the code snippets in your rust docs also compile, and are tested? +/// > now you do! :) `cargo test --doc` will run the tests. +/// +/// ## Example +/// +/// ``` +/// # use pba_entrance_exam::m_builder::EmployeeBuilder; +/// +/// # fn main() { +/// let success = EmployeeBuilder::default().name("John".to_string()).uid(42).build(); +/// assert!(success.is_ok()); +/// +/// let fail = EmployeeBuilder::default().name("John".to_string()).build(); +/// assert!(fail.is_err()); +/// +/// let fail = EmployeeBuilder::default().uid(42).build(); +/// assert!(fail.is_err()); +/// # } +/// ``` +pub struct EmployeeBuilder { + name: Option, + uid: Option, + experience: u32, + wage: u32, +} + +impl Default for EmployeeBuilder { + fn default() -> Self { + Self { + name: None, + uid: None, + wage: 0, + experience: 0, + } + } +} + +impl EmployeeBuilder { + pub fn name(self, name: String) -> Self { + todo!("finish the implementation."); + } + + pub fn uid(self, uid: u32) -> Self { + todo!("finish the implementation."); + } + + pub fn experience(self, experience: u32) -> Self { + todo!("finish the implementation."); + } + + pub fn wage(self, wage: u32) -> Self { + todo!("finish the implementation."); + } + + pub fn build(self) -> Result { + todo!("finish the implementation."); + } +} + +// Okay, that was good, but the unfortunate thing about the previous approach is that we will have +// no way to notify the user about their potential failure to set the name or uid, until they call +// `build` at runtime. Isn't Rust all about using the type system to move runtime errors to compile +// time? +// +// > "Rust is a language that gives you compile-time errors instead of runtime errors. It's like +// > having a spell checker for your code." - Steve Klabnik +// +// With this mindset in mind, we will introduce a new pattern called "type-state" to help us achieve +// that. + +/// A unique type explicitly representing an employee that has been named. +pub struct Named { + name: String, +} +/// A unique type explicitly representing an employee that NOT has been named. +pub struct NotNamed; + +/// A unique type explicitly representing an employee that has been identified. +pub struct Identified { + uid: u32, +} +/// A unique type explicitly representing an employee that has NOT been identified. +pub struct UnIdentified; + +/// A new builder that uses the "type-state" pattern to ensure that the user has set the name and +/// uid. The main trick here is that instead of having `name` be represented by `Option`, we +/// have two unique types mimicking the `Option<_>`: `Named { .. }` is analogous to `Some(_)` and +/// `UnNamed` is analogous to `None`. But, `Option<_>` is jus ONE type, but `Named` and `UnNamed` +/// are two different types. +/// +/// What's the benefit of that? we can make sure that the `fn build` is only implemented if both the +/// `Name` and `Id` generics are set to `Named` and `Identified`. +/// +/// > Did you know that not only you can write tests in your rust-docs, as we did in +/// > [`EmployeeBuilder`], you can also write snippets of code that MUST FAIL TO COMPILE? Cool, eh? +/// > See: +/// +/// ## Example +/// +/// ``` +/// use pba_entrance_exam::m_builder::TypedEmployeeBuilder; +/// +/// # fn main() { +/// // This is not a result anymore, because we guarantee at compile time that it has name and uid. +/// let employee = +/// TypedEmployeeBuilder::default().name("John".to_string()).uid(42).wage(77).build(); +/// assert_eq!(employee.name, "John"); +/// assert_eq!(employee.wage, 77); +/// assert_eq!(employee.uid, 42); +/// # } +/// ``` +/// +/// This code will simply fail to compile: +/// +/// ```compile_fail +/// use pba_entrance_exam::m_builder::TypedEmployeeBuilder; +/// +/// # fn main() { +/// let success = TypedEmployeeBuilder::default().uid(42).build(); +/// # } +/// ``` +pub struct TypedEmployeeBuilder { + experience: u32, + wage: u32, + name: Name, + uid: Id, +} + +impl Default for TypedEmployeeBuilder { + fn default() -> Self { + Self { + experience: 0, + wage: 0, + name: NotNamed, + uid: UnIdentified, + } + } +} + +impl TypedEmployeeBuilder { + pub fn name(self, name: String) -> Self { + todo!("finish the implementation. Note that you might need to move some of these functions to a new `impl` blocks with different trait bounds, or change the return type to use `Named` etc."); + } + + pub fn uid(self, uid: u32) -> Self { + todo!("finish the implementation. Note that you might need to move some of these functions to a new `impl` blocks with different trait bounds, or change the return type to use `Named` etc."); + } + + pub fn experience(self, experience: u32) -> Self { + todo!("finish the implementation. Note that you might need to move some of these functions to a new `impl` blocks with different trait bounds, or change the return type to use `Named` etc."); + } + + pub fn wage(self, wage: u32) -> Self { + todo!("finish the implementation. Note that you might need to move some of these functions to a new `impl` blocks with different trait bounds, or change the return type to use `Named` etc."); + } + + pub fn build(self) -> Employee { + todo!("finish the implementation. Note that you might need to move some of these functions to a new `impl` blocks with different trait bounds, or change the return type to use `Named` etc."); + } +} + +/// This function is not graded. It is just for collecting feedback. +/// On a scale from 0 - 255, with zero being extremely easy and 255 being extremely hard, +/// how hard did you find this section of the exam. +pub fn how_hard_was_this_section() -> u8 { + todo!() +} + +/// This function is not graded. It is just for collecting feedback. +/// How much time (in hours) did you spend on this section of the exam? +pub fn how_many_hours_did_you_spend_on_this_section() -> u8 { + todo!() +}