commit c212a473ba4d060b209b654c0dd403328d6e137f Author: Martin Geisler Date: Wed Dec 21 16:36:30 2022 +0100 Publish Comprehensive Rust 🦀 diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..564055b3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/book/ +/target/ diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..6272489d --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,28 @@ +# How to Contribute + +We'd love to accept your patches and contributions to this project. There are +just a few small guidelines you need to follow. + +## Contributor License Agreement + +Contributions to this project must be accompanied by a Contributor License +Agreement. You (or your employer) retain the copyright to your contribution; +this simply gives us permission to use and redistribute your contributions as +part of the project. Head over to to see +your current agreements on file or to sign a new one. + +You generally only need to submit a CLA once, so if you've already submitted one +(even if it was for a different project), you probably don't need to do it +again. + +## Code Reviews + +All submissions, including submissions by project members, require review. We +use GitHub pull requests for this purpose. Consult +[GitHub Help](https://help.github.com/articles/about-pull-requests/) for more +information on using pull requests. + +## Community Guidelines + +This project follows [Google's Open Source Community +Guidelines](https://opensource.google/conduct/). diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 00000000..a04755a9 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,49 @@ +[package] +name = "comprehensive-rust" +version = "0.1.0" +edition = "2021" +publish = false + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[[bin]] +name = "for-loops" +path = "src/exercises/day-1/for-loops.rs" + +[[bin]] +name = "book-library" +path = "src/exercises/day-1/book-library.rs" + +[[bin]] +name = "points-polygons" +path = "src/exercises/day-2/points-polygons.rs" + +[[bin]] +name = "luhn" +path = "src/exercises/day-2/luhn.rs" + +[[bin]] +name = "strings-iterators" +path = "src/exercises/day-2/strings-iterators.rs" + +[[bin]] +name = "safe-ffi-wrapper" +path = "src/exercises/day-3/safe-ffi-wrapper.rs" + +[[bin]] +name = "simple-gui" +path = "src/exercises/day-3/simple-gui.rs" + +[[bin]] +name = "dining-philosophers" +path = "src/exercises/day-4/dining-philosophers.rs" + +[[bin]] +name = "link-checker" +path = "src/exercises/day-4/link-checker.rs" + + +[dependencies] +reqwest = { version = "0.11.12", features = ["blocking"] } +scraper = "0.13.0" +thiserror = "1.0.37" diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 00000000..d6456956 --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/README.md b/README.md new file mode 100644 index 00000000..41d7cb25 --- /dev/null +++ b/README.md @@ -0,0 +1,40 @@ +# Comprehensive Rust 🦀 + +This repository has the source code for Comprehensive Rust 🦀, a four day Rust +course developed by the Android team. The course covers all aspects of Rust, +from basic syntax to generics and error handling. It also includes +Android-specific content on the last day. + +## Building + +The course is built using [mdBook](https://github.com/rust-lang/mdBook) and its +[Svgbob plugin](https://github.com/boozook/mdbook-svgbob). Install both tools +with + +```shell +$ cargo install mdbook +$ cargo install mdbook-svgbob +``` + +Then run + +```shell +$ mdbook test +``` + +to test all included Rust snippets. Run + +```shell +$ mdbook serve +``` + +to start a web server with the course. You'll find the content on +. You can use `mdbook build` to create a static version +of the course in the `book/` directory. + +## Contact + +For questions or comments, please contact [Martin +Geisler](mailto:mgeisler@google.com) or start a [discussion on +GitHub](https://github.com/google/comprehensive-rust/discussions). We would love +to hear from you. diff --git a/book.toml b/book.toml new file mode 100644 index 00000000..c7485251 --- /dev/null +++ b/book.toml @@ -0,0 +1,23 @@ +[book] +authors = ["Martin Geisler"] +language = "en" +multilingual = false +src = "src" +title = "Comprehensive Rust 🦀" + +[rust] +edition = "2021" + +[preprocessor.svgbob] +class = "bob" + +[output.html] +curly-quotes = true +additional-js = ["ga4.js"] + +[output.html.fold] +enable = true +level = 0 + +[output.html.playground] +editable = true diff --git a/ga4.js b/ga4.js new file mode 100644 index 00000000..4ad383a5 --- /dev/null +++ b/ga4.js @@ -0,0 +1,87 @@ +// Copyright 2022 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Setup cookie consent banner. +var consent = document.createElement('script'); +consent.setAttribute('data-autoload-cookie-consent-bar', 'true'); +consent.setAttribute('src', 'https://www.gstatic.com/brandstudio/kato/cookie_choice_component/cookie_consent_bar.v3.js'); +document.head.appendChild(consent); + +// Load and configure Google Analytics. +var ga4 = document.createElement('script'); +ga4.setAttribute('src', 'https://www.googletagmanager.com/gtag/js?id=G-ZN78TEJMRW'); +document.head.appendChild(ga4); + +window.dataLayer = window.dataLayer || []; +function gtag(){dataLayer.push(arguments);} +gtag('js', new Date()); +gtag('config', 'G-ZN78TEJMRW'); + +// Look through all Playgrounds on the page to determine if the code snippet +// matches one of the. If the code is different from all Playgrounds, we +// conclude that the user modified the Playground before submitting it. +function isPlaygroundCodeModified(code) { + // It sounds expensive to look through every Playground, but there are + // normally at most two Playground instances on a page. + let playgrounds = Array.from(document.querySelectorAll(".playground")); + return playgrounds.every(playground => { + let code_block = playground.querySelector("code"); + if (window.ace && code_block.classList.contains("editable")) { + let editor = window.ace.edit(code_block); + return code != editor.originalCode; + } else { + return code != code_block.textContent; + } + }); +} + +// Monkey-patch the window.fetch function so we can track the Playground +// executions. +const playgroundUrl = 'https://play.rust-lang.org/evaluate.json'; +const { fetch: originalFetch } = window; +window.fetch = async (...args) => { + let [resource, config ] = args; + if (resource != playgroundUrl) { + return originalFetch(resource, config); + } + + const startTime = window.performance.now(); + let endTime, errorMessage; + try { + // The fetch_with_timeout function defaults to a 6000 ms timeout. We use a + // slightly shorter timeout so that we can catch and log the error. + config.signal = AbortSignal.timeout(5500); + let response = await originalFetch(resource, config); + payload = await response.json(); + errorMessage = (payload.error == null) ? null : 'compilation_error'; + // Return object compatible with the unpackign done in book.js. + return {'json': () => payload}; + } catch (error) { + // fetch seems to always return AbortError, despite the example on + // https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal/timeout. + if (error.name == 'AbortError' || error.name == 'TimeoutError') { + error = new Error('timeout'); + } + errorMessage = error.message; + throw error; + } finally { + endTime = window.performance.now(); + let code = JSON.parse(config.body).code; + gtag("event", "playground", { + "modified": isPlaygroundCodeModified(code), + "error": errorMessage, + "latency": (endTime - startTime) / 1000, + }); + } +}; diff --git a/rustfmt.toml b/rustfmt.toml new file mode 100644 index 00000000..26ba63e8 --- /dev/null +++ b/rustfmt.toml @@ -0,0 +1 @@ +max_width = 90 diff --git a/src/SUMMARY.md b/src/SUMMARY.md new file mode 100644 index 00000000..e42dbf56 --- /dev/null +++ b/src/SUMMARY.md @@ -0,0 +1,237 @@ +# Summary + +[Welcome to Comprehensive Rust 🦀](welcome.md) +- [Using Cargo](cargo.md) + - [Rust Ecosystem](cargo/rust-ecosystem.md) + - [Code Samples](cargo/code-samples.md) + - [Running Cargo Locally](cargo/running-locally.md) +- [Course Structure](structure.md) + + +# Day 1: Morning + +---- + +- [Welcome](welcome-day-1.md) + - [What is Rust?](welcome-day-1/what-is-rust.md) +- [Hello World!](hello-world.md) + - [Small Example](hello-world/small-example.md) +- [Why Rust?](why-rust.md) + - [Compile Time Guarantees](why-rust/compile-time.md) + - [Runtime Guarantees](why-rust/runtime.md) + - [Modern Features](why-rust/modern.md) +- [Basic Syntax](basic-syntax.md) + - [Scalar Types](basic-syntax/scalar-types.md) + - [Compound Types](basic-syntax/compound-types.md) + - [References](basic-syntax/references.md) + - [Dangling References](basic-syntax/references-dangling.md) + - [Slices](basic-syntax/slices.md) + - [`String` vs `str`](basic-syntax/string-slices.md) + - [Functions](basic-syntax/functions.md) + - [Methods](basic-syntax/methods.md) + - [Overloading](basic-syntax/functions-interlude.md) +- [Exercises](exercises/day-1/morning.md) + - [Implicit Conversions](exercises/day-1/implicit-conversions.md) + - [Arrays and `for` Loops](exercises/day-1/for-loops.md) + +# Day 1: Afternoon + +- [Variables](basic-syntax/variables.md) + - [Type Inference](basic-syntax/type-inference.md) + - [`static` & `const`](basic-syntax/static-and-const.md)) + - [Scopes and Shadowing](basic-syntax/scopes-shadowing.md) +- [Memory Management](memory-management.md) + - [Stack vs Heap](memory-management/stack-vs-heap.md) + - [Stack Memory](memory-management/stack.md) + - [Manual Memory Management](memory-management/manual.md) + - [Scope-Based Memory Management](memory-management/scope-based.md) + - [Garbage Collection](memory-management/garbage-collection.md) + - [Rust Memory Management](memory-management/rust.md) + - [Comparison](memory-management/comparison.md) +- [Ownership](ownership.md) + - [Move Semantics](ownership/move-semantics.md) + - [Moved Strings in Rust](ownership/moved-strings-rust.md) + - [Double Frees in Modern C++](ownership/double-free-modern-cpp.md) + - [Moves in Function Calls](ownership/moves-function-calls.md) + - [Copying and Cloning](ownership/copy-clone.md) + - [Borrowing](ownership/borrowing.md) + - [Shared and Unique Borrows](ownership/shared-unique-borrows.md) + - [Lifetimes](ownership/lifetimes.md) + - [Lifetimes in Function Calls](ownership/lifetimes-function-calls.md) + - [Lifetimes in Data Structures](ownership/lifetimes-data-structures.md) +- [Exercises](exercises/day-1/afternoon.md) + - [Designing a Library](exercises/day-1/book-library.md) + - [Iterators and Ownership](exercises/day-1/iterators-and-ownership.md) + + +# Day 2: Morning + +---- + +- [Welcome](welcome-day-2.md) +- [Structs](structs.md) + - [Tuple Structs](structs/tuple-structs.md) + - [Field Shorthand Syntax](structs/field-shorthand.md) +- [Enums](enums.md) + - [Variant Payloads](enums/variant-payloads.md) + - [Enum Sizes](enums/sizes.md) +- [Methods](methods.md) + - [Method Receiver](methods/receiver.md) + - [Example](methods/example.md) +- [Pattern Matching](pattern-matching.md) + - [Destructuring Enums](pattern-matching/destructuring-enums.md) + - [Destructuring Structs](pattern-matching/destructuring-structs.md) + - [Destructuring Arrays](pattern-matching/destructuring-arrays.md) + - [Match Guards](pattern-matching/match-guards.md) +- [Exercises](exercises/day-2/morning.md) + - [Health Statistics](exercises/day-2/health-statistics.md) + - [Points and Polygons](exercises/day-2/points-polygons.md) + +# Day 2: Afternoon + +- [Control Flow](control-flow.md) + - [Blocks](control-flow/blocks.md) + - [`if` expressions](control-flow/if-expressions.md) + - [`if let` expressions](control-flow/if-let-expressions.md) + - [`while` expressions](control-flow/while-expressions.md) + - [`while let` expressions](control-flow/while-let-expressions.md) + - [`for` expressions](control-flow/for-expressions.md) + - [`loop` expressions](control-flow/loop-expressions.md) + - [`match` expressions](control-flow/match-expressions.md) + - [`break` & `continue`](control-flow/break-continue.md) +- [Standard Library](std.md) + - [`String`](std/string.md) + - [`Option` and `Result`](std/option-result.md) + - [`Vec`](std/vec.md) + - [`HashMap`](std/hashmap.md) + - [`Box`](std/box.md) + - [`Recursive Data Types`](std/box-recursive.md) + - [`Niche Optimization`](std/box-niche.md) + - [`Rc`](std/rc.md) +- [Modules](modules.md) + - [Visibility](modules/visibility.md) + - [Paths](modules/paths.md) + - [Filesystem Hierarchy](modules/filesystem.md) +- [Exercises](exercises/day-2/afternoon.md) + - [Luhn Algorithm](exercises/day-2/luhn.md) + - [Strings and Iterators](exercises/day-2/strings-iterators.md) + + +# Day 3: Morning + +---- + +- [Welcome](welcome-day-3.md) +- [Traits](traits.md) + - [Deriving Traits](traits/deriving-traits.md) + - [Default Methods](traits/default-methods.md) + - [Important Traits](traits/important-traits.md) + - [`Iterator`](traits/iterator.md) + - [`From` and `Into`](traits/from-into.md) + - [`Read` and `Write`](traits/read-write.md) + - [`Add`, `Mul`, ...](traits/operators.md) + - [`Drop`](traits/drop.md) +- [Generics](generics.md) + - [Generic Data Types](generics/data-types.md) + - [Generic Methods](generics/methods.md) + - [Trait Bounds](generics/trait-bounds.md) + - [`impl Trait`](generics/impl-trait.md) + - [Closures](generics/closures.md) + - [Monomorphization](generics/monomorphization.md) + - [Trait Objects](generics/trait-objects.md) +- [Exercises](exercises/day-3/morning.md) + - [A Simple GUI Library](exercises/day-3/simple-gui.md) + +# Day 3: Afternoon + +- [Error Handling](error-handling.md) + - [Panics](error-handling/panics.md) + - [Catching Stack Unwinding](error-handling/panic-unwind.md) + - [Structured Error Handling](error-handling/result.md) + - [Propagating Errors with `?`](error-handling/try-operator.md) + - [Converting Error Types](error-handling/converting-error-types.md) + - [Deriving Error Enums](error-handling/deriving-error-enums.md) + - [Adding Context to Errors](error-handling/error-contexts.md) +- [Testing](testing.md) + - [Unit Tests](testing/unit-tests.md) + - [Test Modules](testing/test-modules.md) + - [Documentation Tests](testing/doc-tests.md) + - [Integration Tests](testing/integration-tests.md) +- [Unsafe Rust](unsafe.md) + - [Dereferencing Raw Pointers](unsafe/raw-pointers.md) + - [Mutable Static Variables](unsafe/mutable-static-variables.md) + - [Calling Unsafe Functions](unsafe/unsafe-functions.md) + - [Extern Functions](unsafe/extern-functions.md) + - [Unions](unsafe/unions.md) +- [Exercises](exercises/day-3/afternoon.md) + - [Safe FFI Wrapper](exercises/day-3/safe-ffi-wrapper.md) + + +# Day 4: Morning + +---- + +- [Welcome](welcome-day-4.md) +- [Concurrency](concurrency.md) + - [Threads](concurrency/threads.md) + - [Scoped Threads](concurrency/scoped-threads.md) + - [Channels](concurrency/channels.md) + - [Unbounded Channels](concurrency/channels/unbounded.md) + - [Bounded Channels](concurrency/channels/bounded.md) + - [Shared State](concurrency/shared_state.md) + - [`Arc`](concurrency/shared_state/arc.md) + - [`Mutex`](concurrency/shared_state/mutex.md) + - [Example](concurrency/shared_state/example.md) + - [`Send` and `Sync`](concurrency/send-sync.md) + - [`Send`](concurrency/send-sync/send.md) + - [`Sync`](concurrency/send-sync/sync.md) + - [Examples](concurrency/send-sync/examples.md) +- [Exercises](exercises/day-4/morning.md) + - [Dining Philosophers](exercises/day-4/dining-philosophers.md) + - [Multi-threaded Link Checker](exercises/day-4/link-checker.md) + +# Day 4: Afternoon + +---- + +- [Android](android.md) + - [Setup](android/setup.md) + - [Build Rules](android/build-rules.md) + - [Binary](android/build-rules/binary.md) + - [Library](android/build-rules/library.md) + - [AIDL](android/aidl.md) + - [Interface](android/aidl/interface.md) + - [Implementation](android/aidl/implementation.md) + - [Server](android/aidl/server.md) + - [Deploy](android/aidl/deploy.md) + - [Client](android/aidl/client.md) + - [Changing API](android/aidl/changing.md) + - [Logging](android/logging.md) + - [Interoperability](android/interoperability.md) + - [With C](android/interoperability/with-c.md) + - [Calling C with Bindgen](android/interoperability/with-c/bindgen.md) + - [Calling Rust from C](android/interoperability/with-c/rust.md) + - [With C++](android/interoperability/cpp.md)) + - [With Java](android/interoperability/java.md) +- [Exercises](exercises/day-4/afternoon.md) + +# Final Words + +- [Thanks!](thanks.md) +- [Other Resources](other-resources.md) +- [Credits](credits.md) + +---- + +# Solutions + +---- + +- [Solutions](exercises/solutions.md) + - [Day 1 Morning](exercises/day-1/solutions-morning.md) + - [Day 1 Afternoon](exercises/day-1/solutions-afternoon.md) + - [Day 2 Morning](exercises/day-2/solutions-morning.md) + - [Day 2 Afternoon](exercises/day-2/solutions-afternoon.md) + - [Day 3 Morning](exercises/day-3/solutions-morning.md) + - [Day 3 Afternoon](exercises/day-3/solutions-afternoon.md) + - [Day 4 Morning](exercises/day-4/solutions-morning.md) diff --git a/src/android.md b/src/android.md new file mode 100644 index 00000000..c0424d5c --- /dev/null +++ b/src/android.md @@ -0,0 +1,6 @@ +# Android + +Rust is supported for native platform development on Android. This means that +you can write new operating system services in Rust, as well as extending +existing services. + diff --git a/src/android/aidl.md b/src/android/aidl.md new file mode 100644 index 00000000..e620940a --- /dev/null +++ b/src/android/aidl.md @@ -0,0 +1,7 @@ +# AIDL + +The [Android Interface Definition Language +(AIDL)](https://developer.android.com/guide/components/aidl) is support in Rust: + +* Rust code can call existing AIDL servers, +* You can create new AIDL servers in Rust. diff --git a/src/android/aidl/birthday_service/Android.bp b/src/android/aidl/birthday_service/Android.bp new file mode 100644 index 00000000..1768db13 --- /dev/null +++ b/src/android/aidl/birthday_service/Android.bp @@ -0,0 +1,38 @@ +// ANCHOR: libbirthdayservice +rust_library { + name: "libbirthdayservice", + srcs: ["src/lib.rs"], + crate_name: "birthdayservice", + rustlibs: [ + "com.example.birthdayservice-rust", + "libbinder_rs", + ], +} +// ANCHOR_END: libbirthdayservice + +// ANCHOR: birthday_server +rust_binary { + name: "birthday_server", + crate_name: "birthday_server", + srcs: ["src/server.rs"], + rustlibs: [ + "com.example.birthdayservice-rust", + "libbinder_rs", + "libbirthdayservice", + ], + prefer_rlib: true, +} +// ANCHOR_END: birthday_server + +// ANCHOR: birthday_client +rust_binary { + name: "birthday_client", + crate_name: "birthday_client", + srcs: ["src/client.rs"], + rustlibs: [ + "com.example.birthdayservice-rust", + "libbinder_rs", + ], + prefer_rlib: true, +} +// ANCHOR_END: birthday_client diff --git a/src/android/aidl/birthday_service/aidl/Android.bp b/src/android/aidl/birthday_service/aidl/Android.bp new file mode 100644 index 00000000..a03600be --- /dev/null +++ b/src/android/aidl/birthday_service/aidl/Android.bp @@ -0,0 +1,10 @@ +aidl_interface { + name: "com.example.birthdayservice", + srcs: ["com/example/birthdayservice/*.aidl"], + unstable: true, + backend: { + rust: { // Rust is not enabled by default + enabled: true, + }, + }, +} diff --git a/src/android/aidl/birthday_service/aidl/com/example/birthdayservice/IBirthdayService.aidl b/src/android/aidl/birthday_service/aidl/com/example/birthdayservice/IBirthdayService.aidl new file mode 100644 index 00000000..9dfb9588 --- /dev/null +++ b/src/android/aidl/birthday_service/aidl/com/example/birthdayservice/IBirthdayService.aidl @@ -0,0 +1,22 @@ +// Copyright 2022 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// ANCHOR: IBirthdayService +package com.example.birthdayservice; + +/** Birthday service interface. */ +interface IBirthdayService { + /** Generate a Happy Birthday message. */ + String wishHappyBirthday(String name, int years); +} diff --git a/src/android/aidl/birthday_service/src/client.rs b/src/android/aidl/birthday_service/src/client.rs new file mode 100644 index 00000000..0ac3e0df --- /dev/null +++ b/src/android/aidl/birthday_service/src/client.rs @@ -0,0 +1,42 @@ +// Copyright 2022 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// ANCHOR: main +//! Birthday service. +use com_example_birthdayservice::aidl::com::example::birthdayservice::IBirthdayService::IBirthdayService; +use com_example_birthdayservice::binder; + +const SERVICE_IDENTIFIER: &str = "birthdayservice"; + +/// Connect to the BirthdayService. +pub fn connect() -> Result, binder::StatusCode> { + binder::get_interface(SERVICE_IDENTIFIER) +} + +/// Call the birthday service. +fn main() -> Result<(), binder::Status> { + let name = std::env::args() + .nth(1) + .unwrap_or_else(|| String::from("Bob")); + let years = std::env::args() + .nth(2) + .and_then(|arg| arg.parse::().ok()) + .unwrap_or(42); + + binder::ProcessState::start_thread_pool(); + let service = connect().expect("Failed to connect to BirthdayService"); + let msg = service.wishHappyBirthday(&name, years)?; + println!("{msg}"); + Ok(()) +} diff --git a/src/android/aidl/birthday_service/src/lib.rs b/src/android/aidl/birthday_service/src/lib.rs new file mode 100644 index 00000000..88c7f5ac --- /dev/null +++ b/src/android/aidl/birthday_service/src/lib.rs @@ -0,0 +1,31 @@ +// Copyright 2022 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// ANCHOR: IBirthdayService +//! Implementation of the `IBirthdayService` AIDL interface. +use com_example_birthdayservice::aidl::com::example::birthdayservice::IBirthdayService::IBirthdayService; +use com_example_birthdayservice::binder; + +/// The `IBirthdayService` implementation. +pub struct BirthdayService; + +impl binder::Interface for BirthdayService {} + +impl IBirthdayService for BirthdayService { + fn wishHappyBirthday(&self, name: &str, years: i32) -> binder::Result { + Ok(format!( + "Happy Birthday {name}, congratulations with the {years} years!" + )) + } +} diff --git a/src/android/aidl/birthday_service/src/server.rs b/src/android/aidl/birthday_service/src/server.rs new file mode 100644 index 00000000..8393c63f --- /dev/null +++ b/src/android/aidl/birthday_service/src/server.rs @@ -0,0 +1,33 @@ +// Copyright 2022 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// ANCHOR: main +//! Birthday service. +use birthdayservice::BirthdayService; +use com_example_birthdayservice::aidl::com::example::birthdayservice::IBirthdayService::BnBirthdayService; +use com_example_birthdayservice::binder; + +const SERVICE_IDENTIFIER: &str = "birthdayservice"; + +/// Entry point for birthday service. +fn main() { + let birthday_service = BirthdayService; + let birthday_service_binder = BnBirthdayService::new_binder( + birthday_service, + binder::BinderFeatures::default(), + ); + binder::add_service(SERVICE_IDENTIFIER, birthday_service_binder.as_binder()) + .expect("Failed to register service"); + binder::ProcessState::join_thread_pool() +} diff --git a/src/android/aidl/changing.md b/src/android/aidl/changing.md new file mode 100644 index 00000000..37172812 --- /dev/null +++ b/src/android/aidl/changing.md @@ -0,0 +1,14 @@ +# Changing API + +Let us extend the API with more functionality: we want to let clients specify a +list of lines for the birthday card: + +```java +package com.example.birthdayservice; + +/** Birthday service interface. */ +interface IBirthdayService { + /** Generate a Happy Birthday message. */ + String wishHappyBirthday(String name, int years, in String[] text); +} +``` diff --git a/src/android/aidl/client.md b/src/android/aidl/client.md new file mode 100644 index 00000000..3ca68b9d --- /dev/null +++ b/src/android/aidl/client.md @@ -0,0 +1,24 @@ +# AIDL Client + +Finally, we can create a Rust client for our new service. + +*birthday_service/src/client.rs*: + +```rust,ignore +{{#include birthday_service/src/client.rs:main}} +``` + +*birthday_service/Android.bp*: + +```javascript +{{#include birthday_service/Android.bp:birthday_client}} +``` + +Notice that the client does not depend on `libbirthdayservice`. + +Build, push, and run the client on your device: + +```shell +{{#include ../build_all.sh:birthday_client}} +Happy Birthday Charlie, congratulations with the 60 years! +``` diff --git a/src/android/aidl/deploy.md b/src/android/aidl/deploy.md new file mode 100644 index 00000000..3b55fae3 --- /dev/null +++ b/src/android/aidl/deploy.md @@ -0,0 +1,29 @@ +# Deploy + +We can now build, push, and start the service: + +```shell +{{#include ../build_all.sh:birthday_server}} +``` + +In another terminal, check that the service runs: + +```shell +{{#include ../build_all.sh:service_check_birthday_server}} +Service birthdayservice: found +``` + +You can also call the service with `service call`: + +```shell +$ {{#include ../build_all.sh:service_call_birthday_server}} +Result: Parcel( + 0x00000000: 00000000 00000036 00610048 00700070 '....6...H.a.p.p.' + 0x00000010: 00200079 00690042 00740072 00640068 'y. .B.i.r.t.h.d.' + 0x00000020: 00790061 00420020 0062006f 0020002c 'a.y. .B.o.b.,. .' + 0x00000030: 006f0063 0067006e 00610072 00750074 'c.o.n.g.r.a.t.u.' + 0x00000040: 0061006c 00690074 006e006f 00200073 'l.a.t.i.o.n.s. .' + 0x00000050: 00690077 00680074 00740020 00650068 'w.i.t.h. .t.h.e.' + 0x00000060: 00320020 00200034 00650079 00720061 ' .2.4. .y.e.a.r.' + 0x00000070: 00210073 00000000 's.!..... ') +``` diff --git a/src/android/aidl/implementation.md b/src/android/aidl/implementation.md new file mode 100644 index 00000000..89993016 --- /dev/null +++ b/src/android/aidl/implementation.md @@ -0,0 +1,15 @@ +# Service Implementation + +We can now implement the AIDL service: + +*birthday_service/src/lib.rs*: + +```rust,ignore +{{#include birthday_service/src/lib.rs:IBirthdayService}} +``` + +*birthday_service/Android.bp*: + +```javascript +{{#include birthday_service/Android.bp:libbirthdayservice}} +``` diff --git a/src/android/aidl/interface.md b/src/android/aidl/interface.md new file mode 100644 index 00000000..fd754852 --- /dev/null +++ b/src/android/aidl/interface.md @@ -0,0 +1,18 @@ +# AIDL Interfaces + +You declare the API of your service using an AIDL interface: + +*birthday_service/aidl/com/example/birthdayservice/IBirthdayService.aidl*: + +```java +{{#include birthday_service/aidl/com/example/birthdayservice/IBirthdayService.aidl:IBirthdayService}} +``` + +*birthday_service/aidl/Android.bp*: + +```javascript +{{#include birthday_service/aidl/Android.bp}} +``` + +Add `vendor_available: true` if your AIDL file is used by a binary in the vendor +partition. diff --git a/src/android/aidl/server.md b/src/android/aidl/server.md new file mode 100644 index 00000000..de9217a0 --- /dev/null +++ b/src/android/aidl/server.md @@ -0,0 +1,15 @@ +# AIDL Server + +Finally, we can create a server which exposes the service: + +*birthday_service/src/server.rs*: + +```rust,ignore +{{#include birthday_service/src/server.rs:main}} +``` + +*birthday_service/Android.bp*: + +```javascript +{{#include birthday_service/Android.bp:birthday_server}} +``` diff --git a/src/android/bpfmt.sh b/src/android/bpfmt.sh new file mode 100755 index 00000000..012a8817 --- /dev/null +++ b/src/android/bpfmt.sh @@ -0,0 +1,27 @@ +#!/bin/zsh +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Simple wrapper for bpfmt which will remove unnecessary newlines before the +# mdbook anchors. + +if ! type bpfmt > /dev/null; then + echo 'Can not find bpfmt, do you need to run "m bpfmt"?' + exit 1 +fi + +for f in comprehensive_rust/**/Android.bp; do + bpfmt -s -w $f + sed -zi 's|\n// ANCHOR_END|// ANCHOR_END|g' $f +done diff --git a/src/android/build-rules.md b/src/android/build-rules.md new file mode 100644 index 00000000..a451e804 --- /dev/null +++ b/src/android/build-rules.md @@ -0,0 +1,16 @@ +# Build Rules + +The Android build system (Soong) supports Rust via a number of modules: + +| Module Type | Description | +|-------------------|----------------------------------------------------------------------------------------------------| +| `rust_binary` | Produces a Rust binary. | +| `rust_library` | Produces a Rust library, and provides both `rlib` and `dylib` variants. | +| `rust_ffi` | Produces a Rust C library usable by `cc` modules, and provides both static and shared variants. | +| `rust_proc_macro` | Produces a `proc-macro` Rust library. These are analogous to compiler plugins. | +| `rust_test` | Produces a Rust test binary that uses the standard Rust test harness. | +| `rust_fuzz` | Produces a Rust fuzz binary leveraging `libfuzzer`. | +| `rust_protobuf` | Generates source and produces a Rust library that provides an interface for a particular protobuf. | +| `rust_bindgen` | Generates source and produces a Rust library containing Rust bindings to C libraries. | + +We will look at `rust_binary` and `rust_library` next. diff --git a/src/android/build-rules/binary.md b/src/android/build-rules/binary.md new file mode 100644 index 00000000..be2e2821 --- /dev/null +++ b/src/android/build-rules/binary.md @@ -0,0 +1,23 @@ +# Rust Binaries + +Let us start with a simple application. At the root of an AOSP checkout, create +the following files: + +_hello_rust/Android.bp_: + +```javascript +{{#include binary/Android.bp}} +``` + +_hello_rust/src/main.rs_: + +```rust +{{#include binary/src/main.rs:main}} +``` + +You can now build, push, and run the binary: + +```shell +{{#include ../build_all.sh:hello_rust}} +Hello from Rust! +``` diff --git a/src/android/build-rules/binary/Android.bp b/src/android/build-rules/binary/Android.bp new file mode 100644 index 00000000..95962e3d --- /dev/null +++ b/src/android/build-rules/binary/Android.bp @@ -0,0 +1,5 @@ +rust_binary { + name: "hello_rust", + crate_name: "hello_rust", + srcs: ["src/main.rs"], +} diff --git a/src/android/build-rules/binary/src/main.rs b/src/android/build-rules/binary/src/main.rs new file mode 100644 index 00000000..21fc9a48 --- /dev/null +++ b/src/android/build-rules/binary/src/main.rs @@ -0,0 +1,21 @@ +// Copyright 2022 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// ANCHOR: main +//! Rust demo. + +/// Prints a greeting to standard output. +fn main() { + println!("Hello from Rust!"); +} diff --git a/src/android/build-rules/library.md b/src/android/build-rules/library.md new file mode 100644 index 00000000..62c77d94 --- /dev/null +++ b/src/android/build-rules/library.md @@ -0,0 +1,37 @@ +# Rust Libraries + +You use `rust_library` to create a new Rust library for Android. + +Here we declare a dependency on two libraries: + +* `libgreeting`, which we define below, +* `libtextwrap`, which is a crate already vendored in + [`external/rust/crates/`][crates]. + +[crates]: https://cs.android.com/android/platform/superproject/+/master:external/rust/crates/ + +_hello_rust/Android.bp_: + +```javascript +{{#include library/Android.bp}} +``` + +_hello_rust/src/main.rs_: + +```rust,ignore +{{#include library/src/main.rs:main}} +``` + +_hello_rust/src/lib.rs_: + +```rust,ignore +{{#include library/src/lib.rs:greeting}} +``` + +You build, push, and run the binary like before: + +```shell +{{#include ../build_all.sh:hello_rust_with_dep}} +Hello Bob, it is very +nice to meet you! +``` diff --git a/src/android/build-rules/library/Android.bp b/src/android/build-rules/library/Android.bp new file mode 100644 index 00000000..9a8ac77a --- /dev/null +++ b/src/android/build-rules/library/Android.bp @@ -0,0 +1,16 @@ +rust_binary { + name: "hello_rust_with_dep", + crate_name: "hello_rust_with_dep", + srcs: ["src/main.rs"], + rustlibs: [ + "libgreetings", + "libtextwrap", + ], + prefer_rlib: true, +} + +rust_library { + name: "libgreetings", + crate_name: "greetings", + srcs: ["src/lib.rs"], +} diff --git a/src/android/build-rules/library/src/lib.rs b/src/android/build-rules/library/src/lib.rs new file mode 100644 index 00000000..fb4b77ab --- /dev/null +++ b/src/android/build-rules/library/src/lib.rs @@ -0,0 +1,21 @@ +// Copyright 2022 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// ANCHOR: greeting +//! Greeting library. + +/// Greet `name`. +pub fn greeting(name: &str) -> String { + format!("Hello {name}, it is very nice to meet you!") +} diff --git a/src/android/build-rules/library/src/main.rs b/src/android/build-rules/library/src/main.rs new file mode 100644 index 00000000..08dbe44e --- /dev/null +++ b/src/android/build-rules/library/src/main.rs @@ -0,0 +1,24 @@ +// Copyright 2022 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// ANCHOR: main +//! Rust demo. + +use greetings::greeting; +use textwrap::fill; + +/// Prints a greeting to standard output. +fn main() { + println!("{}", fill(&greeting("Bob"), 24)); +} diff --git a/src/android/build_all.sh b/src/android/build_all.sh new file mode 100755 index 00000000..093c6b17 --- /dev/null +++ b/src/android/build_all.sh @@ -0,0 +1,130 @@ +#!/bin/bash +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +set -e + +function run_example() { + while read -r line; do + if [[ "$line" != \#* ]]; then + echo "$line" + eval "${line#$ }" + fi + done +} + +cd $ANDROID_BUILD_TOP +source build/envsetup.sh +lunch aosp_cf_x86_64_phone-userdebug +#acloud reconnect --autoconnect adb + +adb root +adb shell rm -rf '/data/local/tmp/*' + +run_example < + +See the [CXX tutorial][2] for an full example of using this. + +[1]: https://cxx.rs/ +[2]: https://cxx.rs/tutorial.html diff --git a/src/android/interoperability/cpp/overview.svg b/src/android/interoperability/cpp/overview.svg new file mode 120000 index 00000000..0edc67de --- /dev/null +++ b/src/android/interoperability/cpp/overview.svg @@ -0,0 +1 @@ +../../../../third_party/cxx/overview.svg \ No newline at end of file diff --git a/src/android/interoperability/java.md b/src/android/interoperability/java.md new file mode 100644 index 00000000..dde7d7a4 --- /dev/null +++ b/src/android/interoperability/java.md @@ -0,0 +1,39 @@ +# Interoperability with Java + +Java can load shared objects via [Java Native Interface +(JNI)](https://en.wikipedia.org/wiki/Java_Native_Interface). The [`jni` +crate](https://docs.rs/jni/) allows you to create a compatible library. + +First, we create a Rust function to export to Java: + +_interoperability/java/src/lib.rs_: + +```rust,compile_fail +{{#include java/src/lib.rs:hello}} +``` + +_interoperability/java/Android.bp_: + +```javascript +{{#include java/Android.bp:libhello_jni}} +``` + +Finally, we can call this function from Java: + +_interoperability/java/HelloWorld.java_: + +```java +{{#include java/HelloWorld.java:HelloWorld}} +``` + +_interoperability/java/Android.bp_: + +```javascript +{{#include java/Android.bp:helloworld_jni}} +``` + +Finally, you can build, sync, and run the binary: + +```shell +{{#include ../build_all.sh:helloworld_jni}} +``` diff --git a/src/android/interoperability/java/Android.bp b/src/android/interoperability/java/Android.bp new file mode 100644 index 00000000..75f10491 --- /dev/null +++ b/src/android/interoperability/java/Android.bp @@ -0,0 +1,17 @@ +// ANCHOR: libhello_jni +rust_ffi_shared { + name: "libhello_jni", + crate_name: "hello_jni", + srcs: ["src/lib.rs"], + rustlibs: ["libjni"], +} +// ANCHOR_END: libhello_jni + +// ANCHOR: helloworld_jni +java_binary { + name: "helloworld_jni", + srcs: ["HelloWorld.java"], + main_class: "HelloWorld", + required: ["libhello_jni"], +} +// ANCHOR_END: helloworld_jni diff --git a/src/android/interoperability/java/HelloWorld.java b/src/android/interoperability/java/HelloWorld.java new file mode 100644 index 00000000..83a4b70e --- /dev/null +++ b/src/android/interoperability/java/HelloWorld.java @@ -0,0 +1,29 @@ +/* + * Copyright 2022 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// ANCHOR: HelloWorld +class HelloWorld { + private static native String hello(String name); + + static { + System.loadLibrary("hello_jni"); + } + + public static void main(String[] args) { + String output = HelloWorld.hello("Alice"); + System.out.println(output); + } +} diff --git a/src/android/interoperability/java/src/lib.rs b/src/android/interoperability/java/src/lib.rs new file mode 100644 index 00000000..af2acafd --- /dev/null +++ b/src/android/interoperability/java/src/lib.rs @@ -0,0 +1,33 @@ +// Copyright 2022 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// ANCHOR: hello +//! Rust <-> Java FFI demo. + +use jni::objects::{JClass, JString}; +use jni::sys::jstring; +use jni::JNIEnv; + +/// HelloWorld::hello method implementation. +#[no_mangle] +pub extern "system" fn Java_HelloWorld_hello( + env: JNIEnv, + _class: JClass, + name: JString, +) -> jstring { + let input: String = env.get_string(name).unwrap().into(); + let greeting = format!("Hello, {input}!"); + let output = env.new_string(greeting).unwrap(); + output.into_inner() +} diff --git a/src/android/interoperability/with-c.md b/src/android/interoperability/with-c.md new file mode 100644 index 00000000..152fa695 --- /dev/null +++ b/src/android/interoperability/with-c.md @@ -0,0 +1,26 @@ +# Interoperability with C + +Rust has full support for linking object files with a C calling convention. +Similarly, you can export Rust functions and call them from C. + +You can do it by hand if you want: + +```rust +extern "C" { + fn abs(x: i32) -> i32; +} + +fn main() { + let x = -42; + let abs_x = unsafe { abs(x) }; + println!("{x}, {abs_x}"); +} +``` + +We already saw this in the [Safe FFI Wrapper +exercise](../../exercises/day-3/safe-ffi-wrapper.md). + +> This assumes full knowledge of the target platform. Not recommended for +> production. + +We will look at better options next. diff --git a/src/android/interoperability/with-c/bindgen.md b/src/android/interoperability/with-c/bindgen.md new file mode 100644 index 00000000..541b5833 --- /dev/null +++ b/src/android/interoperability/with-c/bindgen.md @@ -0,0 +1,75 @@ +# Using Bindgen + +The [bindgen](https://rust-lang.github.io/rust-bindgen/introduction.html) tool +can auto-generate bindings from a C header file. + +First create a small C library: + +_interoperability/bindgen/libbirthday.h_: + +```c +{{#include bindgen/libbirthday.h:card}} +``` + +_interoperability/bindgen/libbirthday.c_: + +```c +{{#include bindgen/libbirthday.c:print_card}} +``` + +Add this to your `Android.bp` file: + +_interoperability/bindgen/Android.bp_: + +```javascript +{{#include bindgen/Android.bp:libbirthday}} +``` + +Create a wrapper header file for the library (not strictly needed in this +example): + +_interoperability/bindgen/libbirthday_wrapper.h_: + +```c +{{#include bindgen/libbirthday_wrapper.h:include}} +``` + +You can now auto-generate the bindings: + +_interoperability/bindgen/Android.bp_: + +```javascript +{{#include bindgen/Android.bp:libbirthday_bindgen}} +``` + +Finally, we can use the bindings in our Rust program: + +_interoperability/bindgen/Android.bp_: + +```javascript +{{#include bindgen/Android.bp:print_birthday_card}} +``` + +_interoperability/bindgen/main.rs_: + +```rust,compile_fail +{{#include bindgen/main.rs:main}} +``` + +Build, push, and run the binary on your device: + +```shell +{{#include ../../build_all.sh:print_birthday_card}} +``` + +Finally, we can run auto-generated tests to ensure the bindings work: + +_interoperability/bindgen/Android.bp_: + +```javascript +{{#include bindgen/Android.bp:libbirthday_bindgen_test}} +``` + +```shell +{{#include ../../build_all.sh:libbirthday_bindgen_test}} +``` diff --git a/src/android/interoperability/with-c/bindgen/Android.bp b/src/android/interoperability/with-c/bindgen/Android.bp new file mode 100644 index 00000000..6d50789c --- /dev/null +++ b/src/android/interoperability/with-c/bindgen/Android.bp @@ -0,0 +1,36 @@ +// ANCHOR: libbirthday +cc_library { + name: "libbirthday", + srcs: ["libbirthday.c"], +} +// ANCHOR_END: libbirthday + +// ANCHOR: libbirthday_bindgen +rust_bindgen { + name: "libbirthday_bindgen", + crate_name: "birthday_bindgen", + wrapper_src: "libbirthday_wrapper.h", + source_stem: "bindings", + static_libs: ["libbirthday"], +} +// ANCHOR_END: libbirthday_bindgen + +// ANCHOR: libbirthday_bindgen_test +rust_test { + name: "libbirthday_bindgen_test", + srcs: [":libbirthday_bindgen"], + crate_name: "libbirthday_bindgen_test", + test_suites: ["general-tests"], + auto_gen_config: true, + clippy_lints: "none", // Generated file, skip linting + lints: "none", +} +// ANCHOR_END: libbirthday_bindgen_test + +// ANCHOR: print_birthday_card +rust_binary { + name: "print_birthday_card", + srcs: ["main.rs"], + rustlibs: ["libbirthday_bindgen"], +} +// ANCHOR_END: print_birthday_card diff --git a/src/android/interoperability/with-c/bindgen/c-library.md b/src/android/interoperability/with-c/bindgen/c-library.md new file mode 100644 index 00000000..01ddb6db --- /dev/null +++ b/src/android/interoperability/with-c/bindgen/c-library.md @@ -0,0 +1,20 @@ +# Create a C library + +_interoperability/c/libbirthday/Android.bp_: + +```javascript +{{#include c/libbirthday/Android.bp:libbirthday}} +``` + +_interoperability/c/libbirthday/libbirthday.h_: + +```c +{{#include c/libbirthday/libbirthday.h}} +``` + +_interoperability/c/libbirthday/libbirthday.c_: + +```c +{{#include c/libbirthday/libbirthday.c}} +``` + diff --git a/src/android/interoperability/with-c/bindgen/libbirthday.c b/src/android/interoperability/with-c/bindgen/libbirthday.c new file mode 100644 index 00000000..6c3704c4 --- /dev/null +++ b/src/android/interoperability/with-c/bindgen/libbirthday.c @@ -0,0 +1,26 @@ +/* + * Copyright 2022 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// ANCHOR: print_card +#include +#include "libbirthday.h" + +void print_card(const card* card) { + printf("+--------------\n"); + printf("| Happy Birthday %s!\n", card->name); + printf("| Congratulations with the %i years!\n", card->years); + printf("+--------------\n"); +} diff --git a/src/android/interoperability/with-c/bindgen/libbirthday.h b/src/android/interoperability/with-c/bindgen/libbirthday.h new file mode 100644 index 00000000..79e51dd3 --- /dev/null +++ b/src/android/interoperability/with-c/bindgen/libbirthday.h @@ -0,0 +1,23 @@ +/* + * Copyright 2022 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// ANCHOR: card +typedef struct card { + const char* name; + int years; +} card; + +void print_card(const card* card); diff --git a/src/android/interoperability/with-c/bindgen/libbirthday_wrapper.h b/src/android/interoperability/with-c/bindgen/libbirthday_wrapper.h new file mode 100644 index 00000000..15b85bb0 --- /dev/null +++ b/src/android/interoperability/with-c/bindgen/libbirthday_wrapper.h @@ -0,0 +1,18 @@ +/* + * Copyright 2022 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// ANCHOR: include +#include "libbirthday.h" diff --git a/src/android/interoperability/with-c/bindgen/main.rs b/src/android/interoperability/with-c/bindgen/main.rs new file mode 100644 index 00000000..41f4f2af --- /dev/null +++ b/src/android/interoperability/with-c/bindgen/main.rs @@ -0,0 +1,29 @@ +// Copyright 2022 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// ANCHOR: main +//! Bindgen demo. + +use birthday_bindgen::{card, print_card}; + +fn main() { + let name = std::ffi::CString::new("Peter").unwrap(); + let card = card { + name: name.as_ptr(), + years: 42, + }; + unsafe { + print_card(&card as *const card); + } +} diff --git a/src/android/interoperability/with-c/calling-rust.md b/src/android/interoperability/with-c/calling-rust.md new file mode 100644 index 00000000..95afb5db --- /dev/null +++ b/src/android/interoperability/with-c/calling-rust.md @@ -0,0 +1 @@ +# Calling Rust from C diff --git a/src/android/interoperability/with-c/hand-written.md b/src/android/interoperability/with-c/hand-written.md new file mode 100644 index 00000000..abf68e63 --- /dev/null +++ b/src/android/interoperability/with-c/hand-written.md @@ -0,0 +1,21 @@ +# Handwritten FFI + +We can declare external functions by hand: + +```rust +extern "C" { + fn abs(x: i32) -> i32; +} + +fn main() { + let x = -42; + let abs_x = unsafe { abs(x) }; + println!("{x}, {abs_x}"); +} +``` + +We already saw this in the [Safe FFI Wrapper +exercise](../../exercises/day-3/safe-ffi-wrapper.md). + +> This assumes full knowledge of the target platform. Not recommended for +> production. diff --git a/src/android/interoperability/with-c/rust.md b/src/android/interoperability/with-c/rust.md new file mode 100644 index 00000000..ce195af2 --- /dev/null +++ b/src/android/interoperability/with-c/rust.md @@ -0,0 +1,42 @@ +# Calling Rust + +Exporting Rust functions and types to C is easy: + +_interoperability/rust/libanalyze/analyze.rs_ + +```rust,editable +{{#include rust/libanalyze/analyze.rs:analyze_numbers}} +``` + +_interoperability/rust/libanalyze/analyze.h_ + +```c +{{#include rust/libanalyze/analyze.h:analyze_numbers}} +``` + +_interoperability/rust/libanalyze/Android.bp_ + +```javascript +{{#include rust/libanalyze/Android.bp}} +``` + +We can now call this from a C binary: + +_interoperability/rust/analyze/main.c_ + +```c +{{#include rust/analyze/main.c:main}} +``` + +_interoperability/rust/analyze/Android.bp_ + +```javascript +{{#include rust/analyze/Android.bp}} +``` + + +Build, push, and run the binary on your device: + +```shell +{{#include ../../build_all.sh:analyze_numbers}} +``` diff --git a/src/android/interoperability/with-c/rust/analyze/Android.bp b/src/android/interoperability/with-c/rust/analyze/Android.bp new file mode 100644 index 00000000..cf868d79 --- /dev/null +++ b/src/android/interoperability/with-c/rust/analyze/Android.bp @@ -0,0 +1,5 @@ +cc_binary { + name: "analyze_numbers", + srcs: ["main.c"], + static_libs: ["libanalyze_ffi"], +} diff --git a/src/android/interoperability/with-c/rust/analyze/main.c b/src/android/interoperability/with-c/rust/analyze/main.c new file mode 100644 index 00000000..9dacce1e --- /dev/null +++ b/src/android/interoperability/with-c/rust/analyze/main.c @@ -0,0 +1,24 @@ +/* + * Copyright 2022 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// ANCHOR: main +#include "analyze.h" + +int main() { + analyze_numbers(10, 20); + analyze_numbers(123, 123); + return 0; +} diff --git a/src/android/interoperability/with-c/rust/libanalyze/Android.bp b/src/android/interoperability/with-c/rust/libanalyze/Android.bp new file mode 100644 index 00000000..3fe826d5 --- /dev/null +++ b/src/android/interoperability/with-c/rust/libanalyze/Android.bp @@ -0,0 +1,6 @@ +rust_ffi { + name: "libanalyze_ffi", + crate_name: "analyze_ffi", + srcs: ["analyze.rs"], + include_dirs: ["."], +} diff --git a/src/android/interoperability/with-c/rust/libanalyze/analyze.h b/src/android/interoperability/with-c/rust/libanalyze/analyze.h new file mode 100644 index 00000000..51d62779 --- /dev/null +++ b/src/android/interoperability/with-c/rust/libanalyze/analyze.h @@ -0,0 +1,25 @@ +/* + * Copyright 2022 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// ANCHOR: analyze_numbers +#ifndef ANALYSE_H +#define ANALYSE_H + +extern "C" { +void analyze_numbers(int x, int y); +} + +#endif diff --git a/src/android/interoperability/with-c/rust/libanalyze/analyze.rs b/src/android/interoperability/with-c/rust/libanalyze/analyze.rs new file mode 100644 index 00000000..8c6bd03b --- /dev/null +++ b/src/android/interoperability/with-c/rust/libanalyze/analyze.rs @@ -0,0 +1,29 @@ +// Copyright 2022 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// ANCHOR: analyze_numbers +//! Rust FFI demo. +#![deny(improper_ctypes_definitions)] + +use std::os::raw::c_int; + +/// Analyze the numbers. +#[no_mangle] +pub extern "C" fn analyze_numbers(x: c_int, y: c_int) { + if x < y { + println!("x ({x}) is smallest!"); + } else { + println!("y ({y}) is probably larger than x ({x})"); + } +} diff --git a/src/android/logging.md b/src/android/logging.md new file mode 100644 index 00000000..3a3feb19 --- /dev/null +++ b/src/android/logging.md @@ -0,0 +1,31 @@ +# Logging + +You should use the `log` crate to automatically log to `logcat` (on-device) or +`stdout` (on-host): + +_hello_rust_logs/Android.bp_: + +```javascript +{{#include logging/Android.bp}} +``` + +_hello_rust_logs/src/main.rs_: + +```rust,ignore +{{#include logging/src/main.rs:main}} +``` + +Build, push, and run the binary on your device: + +```shell +{{#include build_all.sh:hello_rust_logs}} +``` + +The logs show up in `adb logcat`: + +```shell +$ adb logcat -s rust +09-08 08:38:32.454 2420 2420 D rust: hello_rust_logs: Starting program. +09-08 08:38:32.454 2420 2420 I rust: hello_rust_logs: Things are going fine. +09-08 08:38:32.454 2420 2420 E rust: hello_rust_logs: Something went wrong! +``` diff --git a/src/android/logging/Android.bp b/src/android/logging/Android.bp new file mode 100644 index 00000000..2417d854 --- /dev/null +++ b/src/android/logging/Android.bp @@ -0,0 +1,11 @@ +rust_binary { + name: "hello_rust_logs", + crate_name: "hello_rust_logs", + srcs: ["src/main.rs"], + rustlibs: [ + "liblog_rust", + "liblogger", + ], + prefer_rlib: true, + host_supported: true, +} diff --git a/src/android/logging/src/main.rs b/src/android/logging/src/main.rs new file mode 100644 index 00000000..0a492b4e --- /dev/null +++ b/src/android/logging/src/main.rs @@ -0,0 +1,29 @@ +// Copyright 2022 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// ANCHOR: main +//! Rust logging demo. + +use log::{debug, error}; + +/// Logs a greeting. +fn main() { + logger::init( + logger::Config::default() + .with_tag_on_device("rust") + .with_min_level(log::Level::Trace), + ); + debug!("Starting program."); + error!("Something went wrong!"); +} diff --git a/src/android/setup.md b/src/android/setup.md new file mode 100644 index 00000000..ae1bded5 --- /dev/null +++ b/src/android/setup.md @@ -0,0 +1,13 @@ +# Setup + +We will be using an Android Virtual Device to test our code. Make sure you have +access to one or create a new one with: + +```shell +$ source build/envsetup.sh +$ lunch aosp_cf_x86_64_phone-userdebug +$ acloud create +``` + +Please see the [Android Developer +Codelab](https://source.android.com/docs/setup/start) for details. diff --git a/src/basic-syntax.md b/src/basic-syntax.md new file mode 100644 index 00000000..3bbf8326 --- /dev/null +++ b/src/basic-syntax.md @@ -0,0 +1,9 @@ +# Basic Syntax + +Much of the Rust syntax will be familiar to you from C or C++: + +* Blocks and scopes are delimited by curly braces. +* Line comments are started with `//`, block comments are delimited by `/* ... + */`. +* Keywords like `if` and `while` work the same. +* Variable assigment is done with `=`, comparison is done with `==`. diff --git a/src/basic-syntax/compound-types.md b/src/basic-syntax/compound-types.md new file mode 100644 index 00000000..9708e623 --- /dev/null +++ b/src/basic-syntax/compound-types.md @@ -0,0 +1,26 @@ +# Compound Types + +| | Types | Literals | +|--------|---------------------|--------------------------| +| Arrays | `[T; N]` | `[20, 30, 40]`, `[0; 3]` | +| Tuples | `(T1, T2, T3, ...)` | `('x', 1.2, 0)` | + +Array assignment and access: + +```rust,editable +fn main() { + let mut a: [i8; 10] = [42; 10]; + a[5] = 0; + println!("a: {:?}", a); +} +``` + +Tuple assignment and access: + +```rust,editable +fn main() { + let t: (i8, bool) = (7, true); + println!("1st index: {}", t.0); + println!("2nd index: {}", t.1); +} +``` diff --git a/src/basic-syntax/functions-interlude.md b/src/basic-syntax/functions-interlude.md new file mode 100644 index 00000000..c9638d9c --- /dev/null +++ b/src/basic-syntax/functions-interlude.md @@ -0,0 +1,23 @@ +# Function Overloading + +Overloading is not supported: + +* Each function has a single implementation: + * Always takes a fixed number of parameters. + * Always takes a single set of parameter types. +* Default values are not supported: + * All call sites have the same number of arguments. + * Macros are sometimes used as an alternative. + +However, function parameters can be generic: + +```rust,editable +fn pick_one(a: T, b: T) -> T { + if std::process::id() % 2 == 0 { a } else { b } +} + +fn main() { + println!("coin toss: {}", pick_one("heads", "tails")); + println!("cash prize: {}", pick_one(500, 1000)); +} +``` diff --git a/src/basic-syntax/functions.md b/src/basic-syntax/functions.md new file mode 100644 index 00000000..afcef3b9 --- /dev/null +++ b/src/basic-syntax/functions.md @@ -0,0 +1,31 @@ +# Functions + +A Rust version of the famous FizzBuzz interview question: + +```rust,editable +fn main() { + fizzbuzz_to(20); // Defined below, no forward declaration needed +} + +fn is_divisible_by(lhs: u32, rhs: u32) -> bool { + if rhs == 0 { + return false; // Corner case, early return + } + lhs % rhs == 0 // The last expression is the return value +} + +fn fizzbuzz(n: u32) -> () { // No return value means returning the unit type `()` + match (is_divisible_by(n, 3), is_divisible_by(n, 5)) { + (true, true) => println!("fizzbuzz"), + (true, false) => println!("fizz"), + (false, true) => println!("buzz"), + (false, false) => println!("{n}"), + } +} + +fn fizzbuzz_to(n: u32) { // `-> ()` is normally omitted + for n in 1..=n { + fizzbuzz(n); + } +} +``` diff --git a/src/basic-syntax/methods.md b/src/basic-syntax/methods.md new file mode 100644 index 00000000..d11d2946 --- /dev/null +++ b/src/basic-syntax/methods.md @@ -0,0 +1,30 @@ +# Methods + +Rust has methods, they are simply functions that are associated with a particular type. The +first argument of a method is an instance of the type it is associated with: + +```rust,editable +struct Rectangle { + width: u32, + height: u32, +} + +impl Rectangle { + fn area(&self) -> u32 { + self.width * self.height + } + + fn inc_width(&mut self, delta: u32) { + self.width += delta; + } +} + +fn main() { + let mut rect = Rectangle { width: 10, height: 5 }; + println!("old area: {}", rect.area()); + rect.inc_width(5); + println!("new area: {}", rect.area()); +} +``` + +* We will look much more at methods in today's exercise and in tomorrow's class. diff --git a/src/basic-syntax/references-dangling.md b/src/basic-syntax/references-dangling.md new file mode 100644 index 00000000..efaaa725 --- /dev/null +++ b/src/basic-syntax/references-dangling.md @@ -0,0 +1,19 @@ +# Dangling References + +Rust will statically forbid dangling references: + +```rust,editable,compile_fail +fn main() { + let ref_x: &i32; + { + let x: i32 = 10; + ref_x = &x; + } + println!("ref_x: {ref_x}"); +} +``` + +* A reference is said to "borrow" the value is refers to. +* Rust is tracking the lifetimes of all references to ensure they live long + enough. +* We will talk more about borrowing when we get to ownership. diff --git a/src/basic-syntax/references.md b/src/basic-syntax/references.md new file mode 100644 index 00000000..2c6ba27f --- /dev/null +++ b/src/basic-syntax/references.md @@ -0,0 +1,18 @@ +# References + +Like C++, Rust has references: + +```rust,editable +fn main() { + let mut x: i32 = 10; + let ref_x: &mut i32 = &mut x; + *ref_x = 20; + println!("x: {x}"); +} +``` + +Some differences from C++: + +* We must dereference `ref_x` when assigning to it, similar to C pointers, +* Rust will auto-dereference in some cases, in particular when invoking + methods (try `count_ones`). diff --git a/src/basic-syntax/scalar-types.md b/src/basic-syntax/scalar-types.md new file mode 100644 index 00000000..4cc77f44 --- /dev/null +++ b/src/basic-syntax/scalar-types.md @@ -0,0 +1,18 @@ +# Scalar Types + +| | Types | Literals | +|------------------------|--------------------------------------------|-------------------------------| +| Signed integers | `i8`, `i16`, `i32`, `i64`, `i128`, `isize` | `-10`, `0`, `1_000`, `123i64` | +| Unsigned integers | `u8`, `u16`, `u32`, `u64`, `u128`, `usize` | `0`, `123`, `10u16` | +| Floating point numbers | `f32`, `f64` | `3.14`, `-10.0e20`, `2f32` | +| Strings | `&str` | `"foo"`, `r#"\\"#` | +| Unicode scalar values | `char` | `'a'`, `'α'`, `'∞'` | +| Byte strings | `&[u8]` | `b"abc"`, `br#" " "#` | +| Booleans | `bool` | `true`, `false` | + +The types have widths as follows: + +* `iN`, `uN`, and `fN` are _n_ bits wide, +* `isize` and `usize` are the width of a pointer, +* `char` is 32 bit wide, +* `bool` is 8 bit wide. diff --git a/src/basic-syntax/scopes-shadowing.md b/src/basic-syntax/scopes-shadowing.md new file mode 100644 index 00000000..c585a969 --- /dev/null +++ b/src/basic-syntax/scopes-shadowing.md @@ -0,0 +1,21 @@ +# Scopes and Shadowing + +You can shadow variables, both those from outer scopes and variables from the +same scope: + +```rust,editable +fn main() { + let a = 10; + println!("before: {a}"); + + { + let a = "hello"; + println!("inner scope: {a}"); + + let a = true; + println!("shadowed in inner scope: {a}"); + } + + println!("after: {a}"); +} +``` diff --git a/src/basic-syntax/slices.md b/src/basic-syntax/slices.md new file mode 100644 index 00000000..b703ffc2 --- /dev/null +++ b/src/basic-syntax/slices.md @@ -0,0 +1,16 @@ +# Slices + +A slice gives you a view into a larger collection: + +```rust,editable +fn main() { + let a: [i32; 6] = [10, 20, 30, 40, 50, 60]; + println!("a: {a:?}"); + + let s: &[i32] = &a[2..4]; + println!("s: {s:?}"); +} +``` + +* Slices borrow data from the sliced type. +* Question: What happens if you modify `a[3]`? diff --git a/src/basic-syntax/static-and-const.md b/src/basic-syntax/static-and-const.md new file mode 100644 index 00000000..b1dbd294 --- /dev/null +++ b/src/basic-syntax/static-and-const.md @@ -0,0 +1,39 @@ +# Static and Constant Variables + +Global state is managed with static and constant variables + +## `const` + +You can declare compile-time constants: + +```rust,editable +const DIGEST_SIZE: usize = 3; +const ZERO: Option = Some(42); + +fn compute_digest(text: &str) -> [u8; DIGEST_SIZE] { + let mut digest = [ZERO.unwrap_or(0); DIGEST_SIZE]; + for (idx, &b) in text.as_bytes().iter().enumerate() { + digest[idx % DIGEST_SIZE] = digest[idx % DIGEST_SIZE].wrapping_add(b); + } + digest +} + +fn main() { + let digest = compute_digest("Hello"); + println!("Digest: {digest:?}"); +} +``` + +## `static` + +You can also declare static variables: + +```rust,editable +static BANNER: &str = "Welcome to RustOS 3.14"; + +fn main() { + println!("{BANNER}"); +} +``` + +We will look at mutating static data in the chapter on Unsafe Rust. diff --git a/src/basic-syntax/string-slices.md b/src/basic-syntax/string-slices.md new file mode 100644 index 00000000..629a92b7 --- /dev/null +++ b/src/basic-syntax/string-slices.md @@ -0,0 +1,20 @@ +# `String` vs `str` + +We can now understand the two string types in Rust: + +```rust,editable +fn main() { + let s1: &str = "Hello"; + println!("s1: {s1}"); + + let mut s2: String = String::from("Hello "); + println!("s2: {s2}"); + s2.push_str(s1); + println!("s2: {s2}"); +} +``` + +Rust terminology: + +* `&str` an immutable reference to a string slice. +* `String` a mutable string buffer diff --git a/src/basic-syntax/type-inference.md b/src/basic-syntax/type-inference.md new file mode 100644 index 00000000..5bda958f --- /dev/null +++ b/src/basic-syntax/type-inference.md @@ -0,0 +1,22 @@ +# Type Inference + +Rust will look at how the variable is _used_ to determine the type: + +```rust,editable +fn takes_u32(x: u32) { + println!("u32: {x}"); +} + +fn takes_i8(y: i8) { + println!("i8: {y}"); +} + +fn main() { + let x = 10; + let y = 20; + + takes_u32(x); + takes_i8(y); + // takes_u32(y); +} +``` diff --git a/src/basic-syntax/variables.md b/src/basic-syntax/variables.md new file mode 100644 index 00000000..a4e503f9 --- /dev/null +++ b/src/basic-syntax/variables.md @@ -0,0 +1,13 @@ +# Variables + +Rust provides type safety via static typing. Variable bindings are immutable by +default: + +```rust,editable +fn main() { + let x: i32 = 10; + println!("x: {x}"); + // x = 20; + // println!("x: {x}"); +} +``` diff --git a/src/cargo.md b/src/cargo.md new file mode 100644 index 00000000..452a0aee --- /dev/null +++ b/src/cargo.md @@ -0,0 +1,18 @@ +# Using Cargo + +When you start reading about Rust, you will soon meet Cargo, the standard tool +used in the Rust ecosystem to build and run Rust applications. Here we want to +give a brief overview of what Cargo is and how it fits into the wider ecosystem +and how it fits into this training. + +On Debian/Ubuntu, you can install Cargo and the Rust source with + +```shell +$ sudo apt install cargo rust-src +``` + +This will allow [rust-analyzer][1] to jump to the definitions. We suggest using +[VS Code][2] to edit the code (but any LSP compatible editor works). + +[1]: https://rust-analyzer.github.io/ +[2]: https://code.visualstudio.com/ diff --git a/src/cargo/code-samples.md b/src/cargo/code-samples.md new file mode 100644 index 00000000..0b2c33a1 --- /dev/null +++ b/src/cargo/code-samples.md @@ -0,0 +1,20 @@ +# Code Samples in This Training + +For this training, we will mostly explore the Rust language through examples +which can be executed through your browser. This makes the setup much easier and +ensures a consistent experience for everyone. + +Installing Cargo is still encouraged: it will make it easier for you to do the +exercises. On the last day, we will do a larger exercise which shows you how to +work with dependencies and for that you need Cargo. + +The code blocks in this course are fully interactive: + +```rust,editable +fn main() { + println!("Edit me!"); +} +``` + +You can use Ctrl-Enter to execute the code when focus is in the text +box. diff --git a/src/cargo/running-locally.md b/src/cargo/running-locally.md new file mode 100644 index 00000000..165a7a19 --- /dev/null +++ b/src/cargo/running-locally.md @@ -0,0 +1,66 @@ +# Running Code Locally with Cargo + +If you want to experiment with the code on your own system, then you will need +to first install Rust. Do this by following the [instructions in the Rust +Book][1]. This should give you a working `rustc` and `cargo`. At the time of +writing, the latest stable Rust release has these version numbers: + +```shell +% rustc --version +rustc 1.61.0 (fe5b13d68 2022-05-18) +% cargo --version +cargo 1.61.0 (a028ae4 2022-04-29) +``` + +With this is in place, then follow these steps to build a Rust binary from one +of the examples in this training: + +1. Click the "Copy to clipboard" button on the example you want to copy. + +2. Use `cargo new exercise` to create a new `exercise/` directory for your code: + + ```shell + $ cargo new exercise + Created binary (application) `exercise` package + ``` + +3. Navigate into `exercise/` and use `cargo run` to build and run your binary: + + ```shell + $ cd exercise + $ cargo run + Compiling exercise v0.1.0 (/home/mgeisler/tmp/exercise) + Finished dev [unoptimized + debuginfo] target(s) in 0.75s + Running `target/debug/exercise` + Hello, world! + ``` + +4. Replace the boiler-plate code in `src/main.rs` with your own code. For + example, using the example on the previous page, make `src/main.rs` look like + + ```rust + fn main() { + println!("Edit me!"); + } + ``` + +5. Use `cargo run` to build and run your updated binary: + + ```shell + $ cargo run + Compiling exercise v0.1.0 (/home/mgeisler/tmp/exercise) + Finished dev [unoptimized + debuginfo] target(s) in 0.24s + Running `target/debug/exercise` + Edit me! + ``` + +6. Use `cargo check` to quickly check your project for errors, use `cargo build` + to compile it without running it. You will find the output in `target/debug/` + for a normal debug build. Use `cargo build --release` to produce an optimized + release build in `target/release/`. + +7. You can add dependencies for your project by editing `Cargo.toml`. When you + run `cargo` commands, it will automatically download and compile missing + dependencies for you. + +[1]: https://doc.rust-lang.org/book/ch01-01-installation.html diff --git a/src/cargo/rust-ecosystem.md b/src/cargo/rust-ecosystem.md new file mode 100644 index 00000000..c1c1cabf --- /dev/null +++ b/src/cargo/rust-ecosystem.md @@ -0,0 +1,17 @@ +# The Rust Ecosystem + +The Rust ecosystem consists of a number of tools, of which the main ones are: + +* `rustc`: the Rust compiler which turn `.rs` files into binaries and other + intermediate formats. + +* `cargo`: the Rust dependency manager and build tool. Cargo knows how to + download dependencies hosted on and it will pass them to + `rustc` when building your project. Cargo also comes with a built-in test + runner which is used to execute unit tests. + +* `rustup`: the Rust toolchain installer and updater. This tool is used to + install and update `rustc` and `cargo` when new versions of Rust is released. + In addition, `rustup` can also download documentation for the standard + library. You can have multiple versions of Rust installed at once and `rustup` + will let you switch between them as needed. diff --git a/src/concurrency.md b/src/concurrency.md new file mode 100644 index 00000000..23a084ca --- /dev/null +++ b/src/concurrency.md @@ -0,0 +1,8 @@ +# Fearless Concurrency + +Rust has full support for concurrency using OS threads with mutexes and +channels. + +The Rust type system plays an important role in making many concurrency bugs +compile time bugs. This is often referred to a _fearless concurrency_ since you +can rely on the compiler to ensure correctness at runtime. diff --git a/src/concurrency/channels.md b/src/concurrency/channels.md new file mode 100644 index 00000000..00599cfe --- /dev/null +++ b/src/concurrency/channels.md @@ -0,0 +1,23 @@ +# Channels + +Rust channels have two parts: a `Sender` and a `Receiver`. The two parts +are connected via the channel, but you only see the end-points. + +```rust,editable +use std::sync::mpsc; +use std::thread; + +fn main() { + let (tx, rx) = mpsc::channel(); + + tx.send(10).unwrap(); + tx.send(20).unwrap(); + + println!("Received: {:?}", rx.recv()); + println!("Received: {:?}", rx.recv()); + + let tx2 = tx.clone(); + tx2.send(30).unwrap(); + println!("Received: {:?}", rx.recv()); +} +``` diff --git a/src/concurrency/channels/bounded.md b/src/concurrency/channels/bounded.md new file mode 100644 index 00000000..1bbc38c3 --- /dev/null +++ b/src/concurrency/channels/bounded.md @@ -0,0 +1,27 @@ +# Bounded Channels + +Bounded and synchronous channels make `send` block the current thread: + +```rust,editable +use std::sync::mpsc; +use std::thread; +use std::time::Duration; + +fn main() { + let (tx, rx) = mpsc::sync_channel(3); + + thread::spawn(move || { + let thread_id = thread::current().id(); + for i in 1..10 { + tx.send(format!("Message {i}")).unwrap(); + println!("{thread_id:?}: sent Message {i}"); + } + println!("{thread_id:?}: done"); + }); + thread::sleep(Duration::from_millis(100)); + + for msg in rx.iter() { + println!("Main: got {}", msg); + } +} +``` diff --git a/src/concurrency/channels/unbounded.md b/src/concurrency/channels/unbounded.md new file mode 100644 index 00000000..244bc4d5 --- /dev/null +++ b/src/concurrency/channels/unbounded.md @@ -0,0 +1,27 @@ +# Unbounded Channels + +You get an unbounded and asynchronous channel with `mpsc::channel()`: + +```rust,editable +use std::sync::mpsc; +use std::thread; +use std::time::Duration; + +fn main() { + let (tx, rx) = mpsc::channel(); + + thread::spawn(move || { + let thread_id = thread::current().id(); + for i in 1..10 { + tx.send(format!("Message {i}")).unwrap(); + println!("{thread_id:?}: sent Message {i}"); + } + println!("{thread_id:?}: done"); + }); + thread::sleep(Duration::from_millis(100)); + + for msg in rx.iter() { + println!("Main: got {}", msg); + } +} +``` diff --git a/src/concurrency/scoped-threads.md b/src/concurrency/scoped-threads.md new file mode 100644 index 00000000..ba8f1960 --- /dev/null +++ b/src/concurrency/scoped-threads.md @@ -0,0 +1,33 @@ +# Scoped Threads + +Normal threads cannot borrow from their environment: + +```rust,editable,compile_fail +use std::thread; + +fn main() { + let s = String::from("Hello"); + + thread::spawn(|| { + println!("Length: {}", s.len()); + }); +} +``` + +However, you can use a [scoped thread][1] for this: + +```rust,editable +use std::thread; + +fn main() { + let s = String::from("Hello"); + + thread::scope(|scope| { + scope.spawn(|| { + println!("Length: {}", s.len()); + }); + }); +} +``` + +[1]: https://doc.rust-lang.org/std/thread/fn.scope.html diff --git a/src/concurrency/send-sync.md b/src/concurrency/send-sync.md new file mode 100644 index 00000000..4b6fa237 --- /dev/null +++ b/src/concurrency/send-sync.md @@ -0,0 +1,11 @@ +# `Send` and `Sync` + +How does Rust know to forbid shared access across thread? The answer is in two traits: + +* [`Send`][1]: a type `T` is `Send` if it is safe to move a `T` across a thread + boundary. +* [`Sync`][2]: a type `T` is `Sync` if it is safe to move a `&T` across a thread + boundary. + +[1]: https://doc.rust-lang.org/std/marker/trait.Send.html +[2]: https://doc.rust-lang.org/std/marker/trait.Sync.html diff --git a/src/concurrency/send-sync/examples.md b/src/concurrency/send-sync/examples.md new file mode 100644 index 00000000..adde3f3e --- /dev/null +++ b/src/concurrency/send-sync/examples.md @@ -0,0 +1,41 @@ +# Examples + +## `Send + Sync` + +Most types you come across are `Send + Sync`: + +* `i8`, `f32`, `bool`, `char`, `&str`, ... +* `(T1, T2)`, `[T; N]`, `&[T]`, `struct { x: T }`, ... +* `String`, `Option`, `Vec`, `Box`, ... +* `Arc`: Explicitly thread-safe via atomic reference count. +* `Mutex`: Explicitly thread-safe via internal locking. +* `AtomicBool`, `AtomicU8`, ...: Uses special atomic instructions. + +The generic types are typically `Send + Sync` when the type parameters are +`Send + Sync`. + +## `Send + !Sync` + +These types can be moved to other threads, but they're not thread-safe. +Typically because of interior mutability: + +* `mpsc::Sender` +* `mpsc::Receiver` +* `Cell` +* `RefCell` + +## `!Send + Sync` + +These types are thread-safe, but they cannot be moved to another thread: + +* `MutexGuard`: Uses OS level primitives which must be deallocated on the + thread which created them. + +## `!Send + !Sync` + +These types are not thread-safe and cannot be moved to other threads: + +* `Rc`: each `Rc` has a reference to an `RcBox`, which contains a + non-atomic reference count. +* `*const T`, `*mut T`: Rust that there are special lifetime considerations for the + pointer. diff --git a/src/concurrency/send-sync/send.md b/src/concurrency/send-sync/send.md new file mode 100644 index 00000000..8aa1be26 --- /dev/null +++ b/src/concurrency/send-sync/send.md @@ -0,0 +1,9 @@ +# `Send` + +> A type `T` is [`Send`][1] if it is safe to move a `T` value to another thread. + +The effect of moving ownership to another thread is that _destructors_ will run +in that thread. So the question is when you can allocate a value in one thread +and deallocate it in another. + +[1]: https://doc.rust-lang.org/std/marker/trait.Send.html diff --git a/src/concurrency/send-sync/sync.md b/src/concurrency/send-sync/sync.md new file mode 100644 index 00000000..c91002d3 --- /dev/null +++ b/src/concurrency/send-sync/sync.md @@ -0,0 +1,10 @@ +# `Sync` + +> A type `T` is [`Sync`][1] if it is safe to access a `T` value from multiple +> threads at the same time. + +More precisely, the definitions is + +> `T` is `Sync` if and only if `&T` is `Send` + +[1]: https://doc.rust-lang.org/std/marker/trait.Sync.html diff --git a/src/concurrency/shared_state.md b/src/concurrency/shared_state.md new file mode 100644 index 00000000..1e403d79 --- /dev/null +++ b/src/concurrency/shared_state.md @@ -0,0 +1,11 @@ +# Shared State + +Rust uses the type system to enforce synchronization of shared data. This is +primarily done via two types: + +* [`Arc`][1], atomic reference counted `T`: handled sharing between threads and + takes care to deallocate `T` when the last thread exists, +* [`Mutex`][2]: ensures mutual exclusion for to the `T` value. + +[1]: https://doc.rust-lang.org/std/sync/struct.Arc.html +[2]: https://doc.rust-lang.org/std/sync/struct.Mutex.html diff --git a/src/concurrency/shared_state/arc.md b/src/concurrency/shared_state/arc.md new file mode 100644 index 00000000..3e28f01e --- /dev/null +++ b/src/concurrency/shared_state/arc.md @@ -0,0 +1,25 @@ +# `Arc` + +[`Arc`][1] allows shared read-only access via its `clone` method: + +```rust,editable +use std::thread; +use std::sync::Arc; + +fn main() { + let v = Arc::new(vec![10, 20, 30]); + let mut handles = Vec::new(); + for _ in 1..5 { + let v = v.clone(); + handles.push(thread::spawn(move || { + let thread_id = thread::current().id(); + println!("{thread_id:?}: {v:?}"); + })); + } + + handles.into_iter().for_each(|h| h.join().unwrap()); + println!("v: {v:?}"); +} +``` + +[1]: https://doc.rust-lang.org/std/sync/struct.Arc.html diff --git a/src/concurrency/shared_state/example.md b/src/concurrency/shared_state/example.md new file mode 100644 index 00000000..9d38e28f --- /dev/null +++ b/src/concurrency/shared_state/example.md @@ -0,0 +1,19 @@ +# Example + +Let us see `Arc` and `Mutex` in action: + +```rust,editable,compile_fail +use std::thread; +// use std::sync::{Arc, Mutex}; + +fn main() { + let mut v = vec![10, 20, 30]; + let handle = thread::spawn(|| { + v.push(10); + }); + v.push(1000); + + handle.join().unwrap(); + println!("v: {v:?}"); +} +``` diff --git a/src/concurrency/shared_state/mutex.md b/src/concurrency/shared_state/mutex.md new file mode 100644 index 00000000..08cb1b1c --- /dev/null +++ b/src/concurrency/shared_state/mutex.md @@ -0,0 +1,28 @@ +# `Mutex` + +[`Mutex`][1] ensures mutual exclusion _and_ allows mutable access to `T` +behind a read-only interface: + +```rust,editable +use std::sync::Mutex; + +fn main() { + let v: Mutex> = Mutex::new(vec![10, 20, 30]); + println!("v: {:?}", v.lock().unwrap()); + + { + let v: &Mutex> = &v; + let mut guard = v.lock().unwrap(); + guard.push(40); + } + + println!("v: {:?}", v.lock().unwrap()); +} +``` + +Notice how we have a [`impl Sync for Mutex`][2] blanket +implementation. + +[1]: https://doc.rust-lang.org/std/sync/struct.Mutex.html +[2]: https://doc.rust-lang.org/std/sync/struct.Mutex.html#impl-Sync-for-Mutex%3CT%3E +[3]: https://doc.rust-lang.org/std/sync/struct.Arc.html diff --git a/src/concurrency/threads.md b/src/concurrency/threads.md new file mode 100644 index 00000000..4a56ac0e --- /dev/null +++ b/src/concurrency/threads.md @@ -0,0 +1,26 @@ +# Threads + +Rust threads work similarly to threads in other languages: + +```rust,editable +use std::thread; +use std::time::Duration; + +fn main() { + thread::spawn(|| { + for i in 1..10 { + println!("Count in thread: {i}!"); + thread::sleep(Duration::from_millis(5)); + } + }); + + for i in 1..5 { + println!("Main thread: {i}"); + thread::sleep(Duration::from_millis(5)); + } +} +``` + +* Threads are all daemon threads, the main thread does not wait for them. +* Thread panics are independent of each other. + * Panics can carry a payload, which can be unpacked with `downcast_ref`. diff --git a/src/control-flow.md b/src/control-flow.md new file mode 100644 index 00000000..12cdc96a --- /dev/null +++ b/src/control-flow.md @@ -0,0 +1,6 @@ +# Control Flow + +As we have seen, `if` is an expression in Rust. It is used to conditionally +evaluate one of two blocks, but the blocks can have a value which then becomes +the value of the `if` expression. Other control flow expressions work similarly +in Rust. diff --git a/src/control-flow/blocks.md b/src/control-flow/blocks.md new file mode 100644 index 00000000..f21d3687 --- /dev/null +++ b/src/control-flow/blocks.md @@ -0,0 +1,36 @@ +# Blocks + +A block in Rust has a value and a type: the value is the last expression of the +block: + +```rust,editable +fn main() { + let x = { + let y = 10; + println!("y: {y}"); + let z = { + let w = { + 3 + 4 + }; + println!("w: {w}"); + y * w + }; + println!("z: {z}"); + z - y + }; + println!("x: {x}"); +} +``` + +The same rule is used for functions: the value of the function body is the +return value: + +```rust,editable +fn double(x: i32) -> i32 { + x + x +} + +fn main() { + println!("doubled: {}", double(7)); +} +``` diff --git a/src/control-flow/break-continue.md b/src/control-flow/break-continue.md new file mode 100644 index 00000000..5f7cd83f --- /dev/null +++ b/src/control-flow/break-continue.md @@ -0,0 +1,25 @@ +# `break` and `continue` + +If you want to exit a loop early, use `break`, if you want to immediately start +the next iteration use `continue`. Both `continue` and `break` can optionally +take a label argument which is used to break out of nested loops: + +```rust,editable +fn main() { + let v = vec![10, 20, 30]; + let mut iter = v.into_iter(); + 'outer: while let Some(x) = iter.next() { + println!("x: {x}"); + let mut i = 0; + while i < x { + println!("x: {x}, i: {i}"); + i += 1; + if i == 3 { + break 'outer; + } + } + } +} +``` + +In this case we break the outer loop after 3 iterations of the inner loop. diff --git a/src/control-flow/for-expressions.md b/src/control-flow/for-expressions.md new file mode 100644 index 00000000..eb60704d --- /dev/null +++ b/src/control-flow/for-expressions.md @@ -0,0 +1,16 @@ +# `for` expressions + +The `for` expression is closely related to the `while let` expression. It will +automatically call `into_iter()` on the expression and then iterate over it: + +```rust,editable +fn main() { + let v = vec![10, 20, 30]; + + for x in v { + println!("x: {x}"); + } +} +``` + +You can use `break` and `continue` here as usual. diff --git a/src/control-flow/if-expressions.md b/src/control-flow/if-expressions.md new file mode 100644 index 00000000..4afa2371 --- /dev/null +++ b/src/control-flow/if-expressions.md @@ -0,0 +1,27 @@ +# `if` expressions + +You use `if` very similarly to how you would in other languages: + +```rust,editable +fn main() { + let mut x = 10; + if x % 2 == 0 { + x = x / 2; + } else { + x = 3 * x + 1; + } +} +``` + +In addition, you can use it as an expression. This does the same as above: + +```rust,editable +fn main() { + let mut x = 10; + x = if x % 2 == 0 { + x / 2 + } else { + 3 * x + 1 + }; +} +``` diff --git a/src/control-flow/if-let-expressions.md b/src/control-flow/if-let-expressions.md new file mode 100644 index 00000000..3f44e948 --- /dev/null +++ b/src/control-flow/if-let-expressions.md @@ -0,0 +1,17 @@ +# `if let` expressions + +If you want to match a value against a pattern, you can use `if let`: + +```rust,editable +fn main() { + let arg = std::env::args().next(); + if let Some(value) = arg { + println!("Program name: {value}"); + } else { + println!("Missing name?"); + } +} +``` + +See [pattern matching](../pattern-matching.md) for more details on patterns in +Rust. diff --git a/src/control-flow/loop-expressions.md b/src/control-flow/loop-expressions.md new file mode 100644 index 00000000..26d698c2 --- /dev/null +++ b/src/control-flow/loop-expressions.md @@ -0,0 +1,21 @@ +# `loop` expressions + +Finally, there is a `loop` keyword which creates an endless loop. Here you must +either `break` or `return` to stop the loop: + +```rust,editable +fn main() { + let mut x = 10; + loop { + x = if x % 2 == 0 { + x / 2 + } else { + 3 * x + 1 + }; + if x == 1 { + break; + } + } + println!("Final x: {x}"); +} +``` diff --git a/src/control-flow/match-expressions.md b/src/control-flow/match-expressions.md new file mode 100644 index 00000000..50977ceb --- /dev/null +++ b/src/control-flow/match-expressions.md @@ -0,0 +1,23 @@ +# `match` expressions + +The `match` keyword is used to match a value against one or more patterns. In +that sense, it works like a series of `if let` expressions: + +```rust,editable +fn main() { + match std::env::args().next().as_deref() { + Some("cat") => println!("Will do cat things"), + Some("ls") => println!("Will ls some files"), + Some("mv") => println!("Let's move some files"), + Some("rm") => println!("Uh, dangerous!"), + None => println!("Hmm, no program name?"), + _ => println!("Unknown program name!"), + } +} +``` + +Like `if let`, each match arm must have the same type. The type is the last +expression of the block, if any. In the example above, the type is `()`. + +See [pattern matching](../pattern-matching.md) for more details on patterns in +Rust. diff --git a/src/control-flow/while-expressions.md b/src/control-flow/while-expressions.md new file mode 100644 index 00000000..9ff51d04 --- /dev/null +++ b/src/control-flow/while-expressions.md @@ -0,0 +1,18 @@ +# `while` expressions + +The `while` keyword works very similar to other languages: + +```rust,editable +fn main() { + let mut x = 10; + while x != 1 { + x = if x % 2 == 0 { + x / 2 + } else { + 3 * x + 1 + }; + } + println!("Final x: {x}"); +} +``` + diff --git a/src/control-flow/while-let-expressions.md b/src/control-flow/while-let-expressions.md new file mode 100644 index 00000000..21ccaf82 --- /dev/null +++ b/src/control-flow/while-let-expressions.md @@ -0,0 +1,22 @@ +# `while let` expressions + +Like with `if`, there is a `while let` variant which repeatedly tests a value +against a pattern: + +```rust,editable +fn main() { + let v = vec![10, 20, 30]; + let mut iter = v.into_iter(); + + while let Some(x) = iter.next() { + println!("x: {x}"); + } +} +``` + +Here the iterator returned by `v.iter()` will return a `Option` on every +call to `next()`. It returns `Some(x)` until it is done, after which it will +return `None`. The `while let` lets us keep iterating through all items. + +See [pattern matching](../pattern-matching.md) for more details on patterns in +Rust. diff --git a/src/credits.md b/src/credits.md new file mode 100644 index 00000000..47fe0fbe --- /dev/null +++ b/src/credits.md @@ -0,0 +1,28 @@ +# Credits + +The material here builds on top of the many great sources of Rust documentation. +See the page on [other resources](other-resources.md) for a full list of useful +resources. + +The material of Comprehensive Rust is licensed under the terms of the Apache 2.0 +license, please see [`LICENSE.txt`](../LICENSE.txt) for details. + +## Rust by Example + +Some examples and exercises have been copied and adapted from [Rust by +Example](https://doc.rust-lang.org/rust-by-example/). Please see the +`third_party/rust-by-example/` directory for details, including the license +terms. + +## Rust on Exercism + +Some exercises have been copied and adapted from [Rust on +Exercism](https://exercism.org/tracks/rust). Please see the +`third_party/rust-on-exercism/` directory for details, including the license +terms. + +## CXX + +The [Interoperability with C++](android/interoperability/cpp.md) section uses an +image from [CXX](https://cxx.rs/). Please see the `third_party/cxx/` directory +for details, including the license terms. diff --git a/src/enums.md b/src/enums.md new file mode 100644 index 00000000..b2661bec --- /dev/null +++ b/src/enums.md @@ -0,0 +1,29 @@ +# Enums + +The `enum` keyword allows the creation of a type which has a few +different variants: + +```rust,editable +fn generate_random_number() -> i32 { + 4 // Chosen by fair dice roll. Guaranteed to be random. +} + +#[derive(Debug)] +enum CoinFlip { + Heads, + Tails, +} + +fn flip_coin() -> CoinFlip { + let random_number = generate_random_number(); + if random_number % 2 == 0 { + return CoinFlip::Heads; + } else { + return CoinFlip::Tails; + } +} + +fn main() { + println!("You got: {:?}", flip_coin()); +} +``` diff --git a/src/enums/sizes.md b/src/enums/sizes.md new file mode 100644 index 00000000..37cafc36 --- /dev/null +++ b/src/enums/sizes.md @@ -0,0 +1,25 @@ +# Enum Sizes + +Rust enums are packed tightly, taking constraints due to alignment into account: + +```rust,editable +use std::mem::{align_of, size_of}; + +macro_rules! dbg_size { + ($t:ty) => { + println!("{}: size {} bytes, align: {} bytes", + stringify!($t), size_of::<$t>(), align_of::<$t>()); + }; +} + +enum Foo { + A, + B, +} + +fn main() { + dbg_size!(Foo); +} +``` + +* See the [Rust Reference](https://doc.rust-lang.org/reference/type-layout.html). diff --git a/src/enums/variant-payloads.md b/src/enums/variant-payloads.md new file mode 100644 index 00000000..056ea580 --- /dev/null +++ b/src/enums/variant-payloads.md @@ -0,0 +1,8 @@ +# Variant Payloads + +You can define richer enums where the variants carry data. You can then use the +`match` statement to extract the data from each variant: + +```rust,editable +{{#include ../../third_party/rust-by-example/webevent.rs}} +``` diff --git a/src/error-handling.md b/src/error-handling.md new file mode 100644 index 00000000..ed93e0b4 --- /dev/null +++ b/src/error-handling.md @@ -0,0 +1,7 @@ +# Error Handling + +Error handling in Rust is done using explicit control flow: + +* Functions that can have errors list this in their return type, +* There are no exceptions. + diff --git a/src/error-handling/converting-error-types.md b/src/error-handling/converting-error-types.md new file mode 100644 index 00000000..f60d41c6 --- /dev/null +++ b/src/error-handling/converting-error-types.md @@ -0,0 +1,51 @@ +# Converting Error Types + +The actual expansion of `?` is a little more complicated than previously indicated: + +```rust,ignore +expression? +``` + +actually becomes + +```rust,ignore +match expression { + Ok(value) => value, + Err(err) => return Err(From::from(err)), +} +``` + +The `From::from` call here means we attempt to convert the error type to the +type returned by the function: + +```rust,editable +use std::{fs, io}; +use std::io::Read; + +#[derive(Debug)] +enum ReadUsernameError { + IoError(io::Error), + EmptyUsername(String), +} + +impl From for ReadUsernameError { + fn from(err: io::Error) -> ReadUsernameError { + ReadUsernameError::IoError(err) + } +} + +fn read_username(path: &str) -> Result { + let mut username = String::with_capacity(100); + fs::File::open(path)?.read_to_string(&mut username)?; + if username.is_empty() { + return Err(ReadUsernameError::EmptyUsername(String::from(path))); + } + Ok(username) +} + +fn main() { + //fs::write("config.dat", "").unwrap(); + let username = read_username("config.dat"); + println!("username: {username:?}"); +} +``` diff --git a/src/error-handling/deriving-error-enums.md b/src/error-handling/deriving-error-enums.md new file mode 100644 index 00000000..38deb8db --- /dev/null +++ b/src/error-handling/deriving-error-enums.md @@ -0,0 +1,35 @@ +# Deriving Error Enums + +The [thiserror](https://docs.rs/thiserror/) crate is a popular way to crate an +error enum like we did on the previous page: + +```rust,editable,compile_fail +use std::{fs, io}; +use std::io::Read; +use thiserror::Error; + +#[derive(Error, Debug)] +enum ReadUsernameError { + #[error("Could not read: {0}")] + IoError(#[from] io::Error), + #[error("Found no username in {0}")] + EmptyUsername(String), +} + +fn read_username(path: &str) -> Result { + let mut username = String::with_capacity(100); + fs::File::open(path)?.read_to_string(&mut username)?; + if username.is_empty() { + return Err(ReadUsernameError::EmptyUsername(String::from(path))); + } + Ok(username) +} + +fn main() { + //fs::write("config.dat", "").unwrap(); + match read_username("config.dat") { + Ok(username) => println!("Username: {username}"), + Err(err) => println!("Error: {err}"), + } +} +``` diff --git a/src/error-handling/error-contexts.md b/src/error-handling/error-contexts.md new file mode 100644 index 00000000..065553ed --- /dev/null +++ b/src/error-handling/error-contexts.md @@ -0,0 +1,39 @@ +# Adding Context to Errors + +The widely used [anyhow](https://docs.rs/anyhow/) crate can help you add +contextual information to your errors: + +```rust,editable,compile_fail +use std::{fs, io}; +use std::io::Read; +use thiserror::Error; +use anyhow::{Context, Result}; + +#[derive(Error, Debug)] +enum ReadUsernameError { + #[error("Could not read: {0}")] + IoError(#[from] io::Error), + #[error("Found no username in {0}")] + EmptyUsername(String), +} + +fn read_username(path: &str) -> Result { + let mut username = String::with_capacity(100); + fs::File::open(path) + .context(format!("Failed to open {path}"))? + .read_to_string(&mut username) + .context("Failed to read")?; + if username.is_empty() { + return Err(ReadUsernameError::EmptyUsername(String::from(path)))?; + } + Ok(username) +} + +fn main() { + //fs::write("config.dat", "").unwrap(); + match read_username("config.dat") { + Ok(username) => println!("Username: {username}"), + Err(err) => println!("Error: {err:?}"), + } +} +``` diff --git a/src/error-handling/panic-unwind.md b/src/error-handling/panic-unwind.md new file mode 100644 index 00000000..1038553b --- /dev/null +++ b/src/error-handling/panic-unwind.md @@ -0,0 +1,21 @@ +# Catching the Stack Unwinding + +By default, a panic will cause the stack to unwind. The unwinding can be caught: + +```rust +use std::panic; + +let result = panic::catch_unwind(|| { + println!("hello!"); +}); +assert!(result.is_ok()); + +let result = panic::catch_unwind(|| { + panic!("oh no!"); +}); +assert!(result.is_err()); +``` + +* This can be useful in servers which should keep running even if a single + request crashes. +* This does not work if `panic = abort` is set in your `Cargo.toml`. diff --git a/src/error-handling/panics.md b/src/error-handling/panics.md new file mode 100644 index 00000000..df91c63f --- /dev/null +++ b/src/error-handling/panics.md @@ -0,0 +1,14 @@ +# Panics + +Rust will trigger a panic if a fatal error happens at runtime: + +```rust,editable,should_panic +fn main() { + let v = vec![10, 20, 30]; + println!("v[100]: {}", v[100]); +} +``` + +* Panics are for unrecoverable and unexpected errors. + * Panics are symptoms of bugs in the program. +* Use non-panicking APIs (such as `Vec::get`) if crashing is not acceptable. diff --git a/src/error-handling/result.md b/src/error-handling/result.md new file mode 100644 index 00000000..4daa155a --- /dev/null +++ b/src/error-handling/result.md @@ -0,0 +1,23 @@ +# Structured Error Handling with `Result` + +We have already see the `Result` enum. This is used pervasively when errors are +expected as part of normal operation: + +```rust +use std::fs::File; +use std::io::Read; + +fn main() { + let file = File::open("diary.txt"); + match file { + Ok(mut file) => { + let mut contents = String::new(); + file.read_to_string(&mut contents); + println!("Dear diary: {contents}"); + }, + Err(err) => { + println!("The diary could not be opened: {err}"); + } + } +} +``` diff --git a/src/error-handling/try-operator.md b/src/error-handling/try-operator.md new file mode 100644 index 00000000..a39ed7db --- /dev/null +++ b/src/error-handling/try-operator.md @@ -0,0 +1,46 @@ +# Propagating Errors with `?` + +The try-operator `?` is used to return errors to the caller. It lets you turn +the common + +```rust,ignore +match some_expression { + Ok(value) => value, + Err(err) => return Err(err), +} +``` + +into the much simpler + +```rust,ignore +some_expression? +``` + +We can use this to simplify our error handing code: + +```rust,editable +use std::fs; +use std::io::{self, Read}; + +fn read_username(path: &str) -> Result { + let username_file_result = fs::File::open(path); + + let mut username_file = match username_file_result { + Ok(file) => file, + Err(e) => return Err(e), + }; + + let mut username = String::new(); + + match username_file.read_to_string(&mut username) { + Ok(_) => Ok(username), + Err(e) => Err(e), + } +} + +fn main() { + //fs::write("config.dat", "alice").unwrap(); + let username = read_username("config.dat"); + println!("username: {username:?}"); +} +``` diff --git a/src/exercises/day-1/afternoon.md b/src/exercises/day-1/afternoon.md new file mode 100644 index 00000000..fcb5d46e --- /dev/null +++ b/src/exercises/day-1/afternoon.md @@ -0,0 +1,7 @@ +# Day 1: Afternoon Exercises + +We will look at two things: + +* A small book library, + +* Iterators and ownership (hard). diff --git a/src/exercises/day-1/book-library.md b/src/exercises/day-1/book-library.md new file mode 100644 index 00000000..6678ff5c --- /dev/null +++ b/src/exercises/day-1/book-library.md @@ -0,0 +1,42 @@ +# Designing a Library + +We will learn much more about structs and the `Vec` type tomorrow. For now, +you just need to know part of its API: + +```rust,editable +fn main() { + let mut vec = vec![10, 20]; + vec.push(30); + println!("middle value: {}", vec[vec.len() / 2]); + for item in vec.iter() { + println!("item: {item}"); + } +} +``` + +Use this to create a library application. Copy the code below to + and update the types to make it compile: + +```rust,should_panic +// TODO: remove this when you're done with your implementation. +#![allow(unused_variables, dead_code)] + +{{#include book-library.rs:setup}} + +{{#include book-library.rs:Library_new}} + unimplemented!() + } + +{{#include book-library.rs:Library_len}} + +{{#include book-library.rs:Library_is_empty}} + +{{#include book-library.rs:Library_add_book}} + +{{#include book-library.rs:Library_print_books}} + +{{#include book-library.rs:Library_oldest_book}} +} + +{{#include book-library.rs:main}} +``` diff --git a/src/exercises/day-1/book-library.rs b/src/exercises/day-1/book-library.rs new file mode 100644 index 00000000..d6c0285f --- /dev/null +++ b/src/exercises/day-1/book-library.rs @@ -0,0 +1,168 @@ +// Copyright 2022 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// ANCHOR: setup +struct Library { + books: Vec, +} + +struct Book { + title: String, + year: u16, +} + +impl Book { + // This is a constructor, used below. + fn new(title: &str, year: u16) -> Book { + Book { + title: String::from(title), + year, + } + } +} + +// This makes it possible to print Book values with {}. +impl std::fmt::Display for Book { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{} ({})", self.title, self.year) + } +} +// ANCHOR_END: setup + +// ANCHOR: Library_new +impl Library { + fn new() -> Library { + // ANCHOR_END: Library_new + Library { books: Vec::new() } + } + + // ANCHOR: Library_len + //fn len(self) -> usize { + // unimplemented!() + //} + // ANCHOR_END: Library_len + fn len(&self) -> usize { + self.books.len() + } + + // ANCHOR: Library_is_empty + //fn is_empty(self) -> bool { + // unimplemented!() + //} + // ANCHOR_END: Library_is_empty + fn is_empty(&self) -> bool { + self.books.is_empty() + } + + // ANCHOR: Library_add_book + //fn add_book(self, book: Book) { + // unimplemented!() + //} + // ANCHOR_END: Library_add_book + fn add_book(&mut self, book: Book) { + self.books.push(book) + } + + // ANCHOR: Library_print_books + //fn print_books(self) { + // unimplemented!() + //} + // ANCHOR_END: Library_print_books + fn print_books(&self) { + for book in &self.books { + println!("{}", book); + } + } + + // ANCHOR: Library_oldest_book + //fn oldest_book(self) -> Option<&Book> { + // unimplemented!() + //} + // ANCHOR_END: Library_oldest_book + fn oldest_book(&self) -> Option<&Book> { + self.books.iter().min_by_key(|book| book.year) + } +} + +// ANCHOR: main +fn main() { + // This shows the desired behavior. Uncomment the code below and + // implement the missing methods. You will need to update the + // method signatures, including the "self" parameter! + let library = Library::new(); + + //println!("Our library is empty: {}", library.is_empty()); + // + //library.add_book(Book::new("Lord of the Rings", 1954)); + //library.add_book(Book::new("Alice's Adventures in Wonderland", 1865)); + // + //library.print_books(); + // + //match library.oldest_book() { + // Some(book) => println!("My oldest book is {book}"), + // None => println!("My library is empty!"), + //} + // + //println!("Our library has {} books", library.len()); +} +// ANCHOR_END: main + +#[test] +fn test_library_len() { + let mut library = Library::new(); + assert_eq!(library.len(), 0); + assert!(library.is_empty()); + + library.add_book(Book::new("Lord of the Rings", 1954)); + library.add_book(Book::new("Alice's Adventures in Wonderland", 1865)); + assert_eq!(library.len(), 2); + assert!(!library.is_empty()); +} + +#[test] +fn test_library_is_empty() { + let mut library = Library::new(); + assert!(library.is_empty()); + + library.add_book(Book::new("Lord of the Rings", 1954)); + assert!(!library.is_empty()); +} + +#[test] +fn test_library_print_books() { + let mut library = Library::new(); + library.add_book(Book::new("Lord of the Rings", 1954)); + library.add_book(Book::new("Alice's Adventures in Wonderland", 1865)); + // We could try and capture stdout, but let us just call the + // method to start with. + library.print_books(); +} + +#[test] +fn test_library_oldest_book() { + let mut library = Library::new(); + assert!(library.oldest_book().is_none()); + + library.add_book(Book::new("Lord of the Rings", 1954)); + assert_eq!( + library.oldest_book().map(|b| b.title.as_str()), + Some("Lord of the Rings") + ); + + library.add_book(Book::new("Alice's Adventures in Wonderland", 1865)); + assert_eq!( + library.oldest_book().map(|b| b.title.as_str()), + Some("Alice's Adventures in Wonderland") + ); +} diff --git a/src/exercises/day-1/for-loops.md b/src/exercises/day-1/for-loops.md new file mode 100644 index 00000000..86a5ef18 --- /dev/null +++ b/src/exercises/day-1/for-loops.md @@ -0,0 +1,75 @@ +# Arrays and `for` Loops + +We saw that an array can be declared like this: + +```rust +let array = [10, 20, 30]; +``` + +You can print such an array by asking for its debug representation with `{:?}`: + +```rust,editable +fn main() { + let array = [10, 20, 30]; + println!("array: {array:?}"); +} +``` + +Rust lets you iterate over things like arrays and ranges using the `for` +keyword: + +```rust,editable +fn main() { + let array = [10, 20, 30]; + print!("Iterating over array:"); + for n in array { + print!(" {n}"); + } + println!(); + + print!("Iterating over range:"); + for i in 0..3 { + print!(" {}", array[i]); + } + println!(); +} +``` + +Use the above to write a function `pretty_print` which pretty-print a matrix and +a function `transpose` which will transpose a matrix (turn rows into columns): + +```bob + ⎛⎡1 2 3⎤⎞ ⎡1 4 7⎤ +"transpose"⎜⎢4 5 6⎥⎟ "=="⎢2 5 8⎥ + ⎝⎣7 8 9⎦⎠ ⎣3 6 9⎦ +``` + +Hard-code both functions to operate on 3 × 3 matrices. + +Copy the code below to and implement the +functions: + +```rust,should_panic +// TODO: remove this when you're done with your implementation. +#![allow(unused_variables, dead_code)] + +{{#include for-loops.rs:transpose}} + unimplemented!() +} + +{{#include for-loops.rs:pretty_print}} + unimplemented!() +} + +{{#include for-loops.rs:main}} +``` + +## Bonus Question + +Could you use `&[i32]` slices instead of hard-coded 3 × 3 matrices for your +argument and return types? Something like `&[&[i32]]` for a two-dimensional +slice-of-slices. Why or why not? + + +See the [`ndarray` crate](https://docs.rs/ndarray/) for a production quality +implementation. diff --git a/src/exercises/day-1/for-loops.rs b/src/exercises/day-1/for-loops.rs new file mode 100644 index 00000000..d1c03f13 --- /dev/null +++ b/src/exercises/day-1/for-loops.rs @@ -0,0 +1,69 @@ +// Copyright 2022 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// ANCHOR: transpose +fn transpose(matrix: [[i32; 3]; 3]) -> [[i32; 3]; 3] { + // ANCHOR_END: transpose + let mut result = [[0; 3]; 3]; + for i in 0..3 { + for j in 0..3 { + result[j][i] = matrix[i][j]; + } + } + return result; +} + +// ANCHOR: pretty_print +fn pretty_print(matrix: &[[i32; 3]; 3]) { + // ANCHOR_END: pretty_print + for row in matrix { + println!("{row:?}"); + } +} + +// ANCHOR: tests +#[test] +fn test_transpose() { + let matrix = [ + [101, 102, 103], // + [201, 202, 203], + [301, 302, 303], + ]; + let transposed = transpose(matrix); + assert_eq!( + transposed, + [ + [101, 201, 301], // + [102, 202, 302], + [103, 203, 303], + ] + ); +} +// ANCHOR_END: tests + +// ANCHOR: main +fn main() { + let matrix = [ + [101, 102, 103], // <-- the comment make rustfmt add a newline + [201, 202, 203], + [301, 302, 303], + ]; + + println!("matrix:"); + pretty_print(&matrix); + + let transposed = transpose(matrix); + println!("transposed:"); + pretty_print(&transposed); +} diff --git a/src/exercises/day-1/implicit-conversions.md b/src/exercises/day-1/implicit-conversions.md new file mode 100644 index 00000000..af830e29 --- /dev/null +++ b/src/exercises/day-1/implicit-conversions.md @@ -0,0 +1,41 @@ +# Implicit Conversions + +Rust will not automatically apply _implicit conversions_ between types ([unlike +C++][3]). You can see this in a program like this: + +```rust,editable,compile_fail +fn multiply(x: i16, y: i16) -> i16 { + x * y +} + +fn main() { + let x: i8 = 15; + let y: i16 = 1000; + + println!("{x} * {y} = {}", multiply(x, y)); +} +``` + +The Rust integer types all implement the [`From`][1] and [`Into`][2] +traits to let us convert between them. The `From` trait has a single `from()` +method and similarly, the `Into` trait has a single `into()` method. +Implementing these traits is how a type expresses that it can be converted into +another type. + +The standard library has an implementation of `From for i16`, which means +that we can convert an `i8` to an `i16` by calling the `into()` method on the +`i8`. + +1. Execute the above program and look at the compiler error. + +2. Update the code above to use `into()` to do the conversion. + +3. Change the types of `x` and `y` to other things (such as `f32`, `bool`, + `i128`) to see which types you can convert to which other types. Try + converting small types to big types and the other way around. Check the + [standard library documentation][1] to see if `From` is implemented for + the pairs you check. + +[1]: https://doc.rust-lang.org/std/convert/trait.From.html +[2]: https://doc.rust-lang.org/std/convert/trait.Into.html +[3]: https://en.cppreference.com/w/cpp/language/implicit_conversion diff --git a/src/exercises/day-1/iterators-and-ownership.md b/src/exercises/day-1/iterators-and-ownership.md new file mode 100644 index 00000000..6034ba7e --- /dev/null +++ b/src/exercises/day-1/iterators-and-ownership.md @@ -0,0 +1,110 @@ +# Iterators and Ownership + +The ownership model of Rust affects many APIs. An example of this is the +[`Iterator`](https://doc.rust-lang.org/std/iter/trait.Iterator.html) and +[`IntoIterator`](https://doc.rust-lang.org/std/iter/trait.IntoIterator.html) +traits. + +## `Iterator` + +Traits are like interfaces: they describe behavior (methods) for a type. The +`Iterator` trait simply says that you can call `next` until you get `None` back: + +```rust +pub trait Iterator { + type Item; + fn next(&mut self) -> Option; +} +``` + +You use this trait like this: + +```rust,editable +fn main() { + let v: Vec = vec![10, 20, 30]; + let mut iter = v.iter(); + + println!("v[0]: {:?}", iter.next()); + println!("v[1]: {:?}", iter.next()); + println!("v[2]: {:?}", iter.next()); + println!("No more items: {:?}", iter.next()); +} +``` + +What is the type returned by the iterator? Test your answer here: + +```rust,editable,compile_fail +fn main() { + let v: Vec = vec![10, 20, 30]; + let mut iter = v.iter(); + + let v0: Option<..> = iter.next(); + println!("v0: {v0:?}"); +} +``` + +Why is this type used? + +## `IntoIterator` + +The `Iterator` trait tells you how to _iterate_ once you have created an +iterator. The related trait `IntoIterator` tells you how to create the iterator: + +```rust +pub trait IntoIterator { + type Item; + type IntoIter: Iterator; + + fn into_iter(self) -> Self::IntoIter; +} +``` + +The syntax here means that every implementation of `IntoIterator` must +declare two types: + +* `Item`: the type we iterate over, such as `i8`, +* `IntoIter`: the `Iterator` type returned by the `into_iter` method. + +Note that `IntoIter` and `Item` are linked: the iterator must have the same +`Item` type, which means that it returns `Option` + +Like before, what is the type returned by the iterator? + +```rust,editable,compile_fail +fn main() { + let v: Vec = vec![String::from("foo"), String::from("bar")]; + let mut iter = v.into_iter(); + + let v0: Option<..> = iter.next(); + println!("v0: {v0:?}"); +} +``` + +## `for` Loops + +Now that we know both `Iterator` and `IntoIterator`, we can build `for` loops. +They call `into_iter()` on an expression and iterates over the resulting +iterator: + +```rust,editable +fn main() { + let v: Vec = vec![String::from("foo"), String::from("bar")]; + + for word in &v { + println!("word: {word}"); + } + + for word in v { + println!("word: {word}"); + } +} +``` + +What is the type of `word` in each loop? + +Experiment with the code above and then consult the documentation for [`impl +IntoIterator for +&Vec`](https://doc.rust-lang.org/std/vec/struct.Vec.html#impl-IntoIterator-2) +and [`impl IntoIterator for +Vec`](https://doc.rust-lang.org/std/vec/struct.Vec.html#impl-IntoIterator-1) +to check your answers. diff --git a/src/exercises/day-1/morning.md b/src/exercises/day-1/morning.md new file mode 100644 index 00000000..1f80e321 --- /dev/null +++ b/src/exercises/day-1/morning.md @@ -0,0 +1,7 @@ +# Day 1: Morning Exercises + +In these exercises, we will explore two parts of Rust: + +* Implicit conversions between types. + +* Arrays and `for` loops. diff --git a/src/exercises/day-1/solutions-afternoon.md b/src/exercises/day-1/solutions-afternoon.md new file mode 100644 index 00000000..42366aeb --- /dev/null +++ b/src/exercises/day-1/solutions-afternoon.md @@ -0,0 +1,9 @@ +# Day 1 Afternoon Exercises + +## Designing a Library + +([back to exercise](book-library.md)) + +```rust +{{#include book-library.rs}} +``` diff --git a/src/exercises/day-1/solutions-morning.md b/src/exercises/day-1/solutions-morning.md new file mode 100644 index 00000000..fce004c7 --- /dev/null +++ b/src/exercises/day-1/solutions-morning.md @@ -0,0 +1,9 @@ +# Day 1 Morning Exercises + +## Arrays and `for` Loops + +([back to exercise](for-loops.md)) + +```rust +{{#include for-loops.rs}} +``` diff --git a/src/exercises/day-2/afternoon.md b/src/exercises/day-2/afternoon.md new file mode 100644 index 00000000..ebda99e7 --- /dev/null +++ b/src/exercises/day-2/afternoon.md @@ -0,0 +1,3 @@ +# Day 2: Afternoon Exercises + +The exercises for this afternoon will focus on strings and iterators. diff --git a/src/exercises/day-2/health-statistics.md b/src/exercises/day-2/health-statistics.md new file mode 100644 index 00000000..800adb9c --- /dev/null +++ b/src/exercises/day-2/health-statistics.md @@ -0,0 +1,32 @@ +# Health Statistics + +{{#include ../../../third_party/rust-on-exercism/health-statistics.md}} + +Copy the code below to and fill in the missing +methods: + +```rust,should_panic +// TODO: remove this when you're done with your implementation. +#![allow(unused_variables, dead_code)] + +{{#include ../../../third_party/rust-on-exercism/health-statistics.rs}} + +fn main() { + let bob = User::new(String::from("Bob"), 32, 155.2); + println!("I'm {} and my age is {}", bob.name(), bob.age()); +} + +#[test] +fn test_weight() { + let bob = User::new(String::from("Bob"), 32, 155.2); + assert_eq!(bob.weight(), 155.2); +} + +#[test] +fn test_set_age() { + let mut bob = User::new(String::from("Bob"), 32, 155.2); + assert_eq!(bob.age(), 32); + bob.set_age(33); + assert_eq!(bob.age(), 33); +} +``` diff --git a/src/exercises/day-2/luhn.md b/src/exercises/day-2/luhn.md new file mode 100644 index 00000000..903717ec --- /dev/null +++ b/src/exercises/day-2/luhn.md @@ -0,0 +1,35 @@ +# Luhn Algorithm + +The [Luhn algorithm](https://en.wikipedia.org/wiki/Luhn_algorithm) is used to +validate credit card numbers. The algorithm takes a string as input and does the +following to validate the credit card number: + +* Ignore all spaces. Reject number with less than two digits. + +* Moving from right to left, double every second digit: for the number `1234`, + we double `3` and `1`. + +* After doubling a digit, sum the digits. So doubling `7` becomes `14` which + becomes `5`. + +* Sum all the undoubled and doubled digits. + +* The credit card number is valid if the sum is ends with `0`. + +Copy the following code to and implement the +function: + + +```rust +// TODO: remove this when you're done with your implementation. +#![allow(unused_variables, dead_code)] + +{{#include luhn.rs:luhn}} + unimplemented!() +} + +{{#include luhn.rs:unit-tests}} + +#[allow(dead_code)] +fn main() {} +``` diff --git a/src/exercises/day-2/luhn.rs b/src/exercises/day-2/luhn.rs new file mode 100644 index 00000000..420e4d86 --- /dev/null +++ b/src/exercises/day-2/luhn.rs @@ -0,0 +1,88 @@ +// Copyright 2022 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// ANCHOR: luhn +pub fn luhn(cc_number: &str) -> bool { + // ANCHOR_END: luhn + let mut digits_seen = 0; + let mut sum = 0; + for (i, ch) in cc_number.chars().rev().filter(|&ch| ch != ' ').enumerate() { + match ch.to_digit(10) { + Some(d) => { + sum += if i % 2 == 1 { + let dd = d * 2; + dd / 10 + dd % 10 + } else { + d + }; + digits_seen += 1; + } + None => return false, + } + } + + if digits_seen < 2 { + return false; + } + + sum % 10 == 0 +} + +fn main() { + let cc_number = "1234 5678 1234 5670"; + println!( + "Is {} a valid credit card number? {}", + cc_number, + if luhn(cc_number) { "yes" } else { "no" } + ); +} + +// ANCHOR: unit-tests +#[test] +fn test_non_digit_cc_number() { + assert!(!luhn("foo")); +} + +#[test] +fn test_empty_cc_number() { + assert!(!luhn("")); + assert!(!luhn(" ")); + assert!(!luhn(" ")); + assert!(!luhn(" ")); +} + +#[test] +fn test_single_digit_cc_number() { + assert!(!luhn("0")); +} + +#[test] +fn test_two_digit_cc_number() { + assert!(luhn(" 0 0 ")); +} + +#[test] +fn test_valid_cc_number() { + assert!(luhn("4263 9826 4026 9299")); + assert!(luhn("4539 3195 0343 6467")); + assert!(luhn("7992 7398 713")); +} + +#[test] +fn test_invalid_cc_number() { + assert!(!luhn("4223 9826 4026 9299")); + assert!(!luhn("4539 3195 0343 6476")); + assert!(!luhn("8273 1232 7352 0569")); +} +// ANCHOR_END: unit-tests diff --git a/src/exercises/day-2/morning.md b/src/exercises/day-2/morning.md new file mode 100644 index 00000000..26705389 --- /dev/null +++ b/src/exercises/day-2/morning.md @@ -0,0 +1,7 @@ +# Day 2: Morning Exercises + +We will look at implementing methods in two contexts: + +* Simple struct which tracks health statistics. + +* Multiple structs and enums for a drawing library. diff --git a/src/exercises/day-2/points-polygons.md b/src/exercises/day-2/points-polygons.md new file mode 100644 index 00000000..06a8ac43 --- /dev/null +++ b/src/exercises/day-2/points-polygons.md @@ -0,0 +1,41 @@ +# Polygon Struct + +We will create a `Polygon` struct which contain some points. Copy the code below +to and fill in the missing methods to make the +tests pass: + +```rust +// TODO: remove this when you're done with your implementation. +#![allow(unused_variables, dead_code)] + +{{#include points-polygons.rs:Point}} + // add fields +} + +{{#include points-polygons.rs:Point-impl}} + // add methods +} + +{{#include points-polygons.rs:Polygon}} + // add fields +} + +{{#include points-polygons.rs:Polygon-impl}} + // add methods +} + +{{#include points-polygons.rs:Circle}} + // add fields +} + +{{#include points-polygons.rs:Circle-impl}} + // add methods +} + +{{#include points-polygons.rs:Shape}} + +{{#include points-polygons.rs:unit-tests}} + +#[allow(dead_code)] +fn main() {} +``` diff --git a/src/exercises/day-2/points-polygons.rs b/src/exercises/day-2/points-polygons.rs new file mode 100644 index 00000000..78fd13fb --- /dev/null +++ b/src/exercises/day-2/points-polygons.rs @@ -0,0 +1,223 @@ +// Copyright 2022 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +// ANCHOR: Point +pub struct Point { + // ANCHOR_END: Point + x: i32, + y: i32, +} + +// ANCHOR: Point-impl +impl Point { + // ANCHOR_END: Point-impl + pub fn new(x: i32, y: i32) -> Point { + Point { x, y } + } + + pub fn magnitude(self) -> f64 { + f64::from(self.x.pow(2) + self.y.pow(2)).sqrt() + } + + pub fn dist(self, other: Point) -> f64 { + (self - other).magnitude() + } +} + +impl std::ops::Add for Point { + type Output = Self; + + fn add(self, other: Self) -> Self::Output { + Self { + x: self.x + other.x, + y: self.y + other.y, + } + } +} + +impl std::ops::Sub for Point { + type Output = Self; + + fn sub(self, other: Self) -> Self::Output { + Self { + x: self.x - other.x, + y: self.y - other.y, + } + } +} + +// ANCHOR: Polygon +pub struct Polygon { + // ANCHOR_END: Polygon + points: Vec, +} + +// ANCHOR: Polygon-impl +impl Polygon { + // ANCHOR_END: Polygon-impl + pub fn new() -> Polygon { + Polygon { points: Vec::new() } + } + + pub fn add_point(&mut self, point: Point) { + self.points.push(point); + } + + pub fn left_most_point(&self) -> Option { + self.points.iter().min_by_key(|p| p.x).copied() + } + + pub fn iter(&self) -> impl Iterator { + self.points.iter() + } + + pub fn length(&self) -> f64 { + if self.points.is_empty() { + return 0.0; + } + + let mut result = 0.0; + let mut last_point = self.points[0]; + for point in &self.points[1..] { + result += last_point.dist(*point); + last_point = *point; + } + result += last_point.dist(self.points[0]); + result + } +} + +// ANCHOR: Circle +pub struct Circle { + // ANCHOR_END: Circle + center: Point, + radius: i32, +} + +// ANCHOR: Circle-impl +impl Circle { + // ANCHOR_END: Circle-impl + pub fn new(center: Point, radius: i32) -> Circle { + Circle { center, radius } + } + + pub fn circumference(&self) -> f64 { + 2.0 * std::f64::consts::PI * f64::from(self.radius) + } + + pub fn dist(&self, other: &Self) -> f64 { + self.center.dist(other.center) + } +} + +// ANCHOR: Shape +pub enum Shape { + Polygon(Polygon), + Circle(Circle), +} +// ANCHOR_END: Shape + +impl From for Shape { + fn from(poly: Polygon) -> Self { + Shape::Polygon(poly) + } +} + +impl From for Shape { + fn from(circle: Circle) -> Self { + Shape::Circle(circle) + } +} + +impl Shape { + pub fn circumference(&self) -> f64 { + match self { + Shape::Polygon(poly) => poly.length(), + Shape::Circle(circle) => circle.circumference(), + } + } +} + +// ANCHOR: unit-tests +#[cfg(test)] +mod tests { + use super::*; + + fn round_two_digits(x: f64) -> f64 { + (x * 100.0).round() / 100.0 + } + + #[test] + fn test_point_magnitude() { + let p1 = Point::new(12, 13); + assert_eq!(round_two_digits(p1.magnitude()), 17.69); + } + + #[test] + fn test_point_dist() { + let p1 = Point::new(10, 10); + let p2 = Point::new(14, 13); + assert_eq!(round_two_digits(p1.dist(p2)), 5.00); + } + + #[test] + fn test_point_add() { + let p1 = Point::new(16, 16); + let p2 = p1 + Point::new(-4, 3); + assert_eq!(p2, Point::new(12, 19)); + } + + #[test] + fn test_polygon_left_most_point() { + let p1 = Point::new(12, 13); + let p2 = Point::new(16, 16); + + let mut poly = Polygon::new(); + poly.add_point(p1); + poly.add_point(p2); + assert_eq!(poly.left_most_point(), Some(p1)); + } + + #[test] + fn test_polygon_iter() { + let p1 = Point::new(12, 13); + let p2 = Point::new(16, 16); + + let mut poly = Polygon::new(); + poly.add_point(p1); + poly.add_point(p2); + + let points = poly.iter().cloned().collect::>(); + assert_eq!(points, vec![Point::new(12, 13), Point::new(16, 16)]); + } + + #[test] + fn test_shape_circumferences() { + let mut poly = Polygon::new(); + poly.add_point(Point::new(12, 13)); + poly.add_point(Point::new(16, 16)); + let shapes = vec![ + Shape::from(poly), + Shape::from(Circle::new(Point::new(10, 20), 5)), + ]; + let circumferences = shapes + .iter() + .map(Shape::circumference) + .map(round_two_digits) + .collect::>(); + assert_eq!(circumferences, vec![10.0, 31.42]); + } +} +// ANCHOR_END: unit-tests diff --git a/src/exercises/day-2/solutions-afternoon.md b/src/exercises/day-2/solutions-afternoon.md new file mode 100644 index 00000000..bf9601bd --- /dev/null +++ b/src/exercises/day-2/solutions-afternoon.md @@ -0,0 +1,17 @@ +# Day 2 Afternoon Exercises + +## Luhn Algorithm + +([back to exercise](luhn.md)) + +```rust +{{#include luhn.rs}} +``` + +## Strings and Iterators + +([back to exercise](strings-iterators.md)) + +```rust +{{#include strings-iterators.rs}} +``` diff --git a/src/exercises/day-2/solutions-morning.md b/src/exercises/day-2/solutions-morning.md new file mode 100644 index 00000000..37c5ca17 --- /dev/null +++ b/src/exercises/day-2/solutions-morning.md @@ -0,0 +1,12 @@ +# Day 2 Morning Exercises + +## Points and Polygons + +([back to exercise](points-polygons.md)) + +```rust +{{#include points-polygons.rs}} + +#[allow(dead_code)] +fn main() {} +``` diff --git a/src/exercises/day-2/strings-iterators.md b/src/exercises/day-2/strings-iterators.md new file mode 100644 index 00000000..fd92acdf --- /dev/null +++ b/src/exercises/day-2/strings-iterators.md @@ -0,0 +1,21 @@ +# Strings and Iterators + +In this exercise, you are implementing a routing component of a web server. The +server is configured with a number of _path prefixes_ which are matched against +_request paths_. The path prefixes can contain a wildcard character which +matches a full segment. See the unit tests below. + +Copy the following code to and make the tests +pass. Try avoiding allocating a `Vec` for your intermediate results: + + +```rust +// TODO: remove this when you're done with your implementation. +#![allow(unused_variables, dead_code)] + +{{#include strings-iterators.rs:prefix_matches}} + unimplemented!() +} + +{{#include strings-iterators.rs:unit-tests}} +``` diff --git a/src/exercises/day-2/strings-iterators.rs b/src/exercises/day-2/strings-iterators.rs new file mode 100644 index 00000000..6ac9c06e --- /dev/null +++ b/src/exercises/day-2/strings-iterators.rs @@ -0,0 +1,75 @@ +// Copyright 2022 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// ANCHOR: prefix_matches +pub fn prefix_matches(prefix: &str, request_path: &str) -> bool { + // ANCHOR_END: prefix_matches + let mut prefixes = prefix + .split('/') + .map(|p| Some(p)) + .chain(std::iter::once(None)); + let mut request_paths = request_path + .split('/') + .map(|p| Some(p)) + .chain(std::iter::once(None)); + + for (prefix, request_path) in prefixes.by_ref().zip(&mut request_paths) { + match (prefix, request_path) { + (Some(prefix), Some(request_path)) => { + if (prefix != "*") && (prefix != request_path) { + return false; + } + } + (Some(_), None) => return false, + (None, None) => break, + (None, Some(_)) => break, + } + } + true +} + +// ANCHOR: unit-tests +#[test] +fn test_matches_without_wildcard() { + assert!(prefix_matches("/v1/publishers", "/v1/publishers")); + assert!(prefix_matches("/v1/publishers", "/v1/publishers/abc-123")); + assert!(prefix_matches("/v1/publishers", "/v1/publishers/abc/books")); + + assert!(!prefix_matches("/v1/publishers", "/v1")); + assert!(!prefix_matches("/v1/publishers", "/v1/publishersBooks")); + assert!(!prefix_matches("/v1/publishers", "/v1/parent/publishers")); +} + +#[test] +fn test_matches_with_wildcard() { + assert!(prefix_matches( + "/v1/publishers/*/books", + "/v1/publishers/foo/books" + )); + assert!(prefix_matches( + "/v1/publishers/*/books", + "/v1/publishers/bar/books" + )); + assert!(prefix_matches( + "/v1/publishers/*/books", + "/v1/publishers/foo/books/book1" + )); + + assert!(!prefix_matches("/v1/publishers/*/books", "/v1/publishers")); + assert!(!prefix_matches( + "/v1/publishers/*/books", + "/v1/publishers/foo/booksByAuthor" + )); +} +// ANCHOR_END: unit-tests diff --git a/src/exercises/day-3/afternoon.md b/src/exercises/day-3/afternoon.md new file mode 100644 index 00000000..eae9c189 --- /dev/null +++ b/src/exercises/day-3/afternoon.md @@ -0,0 +1,3 @@ +# Day 3: Afternoon Exercises + +Let us build a safe wrapper for reading directory content! diff --git a/src/exercises/day-3/morning.md b/src/exercises/day-3/morning.md new file mode 100644 index 00000000..d006a7ef --- /dev/null +++ b/src/exercises/day-3/morning.md @@ -0,0 +1,3 @@ +# Day 3: Morning Exercises + +We will design a classical GUI library traits and trait objects. diff --git a/src/exercises/day-3/safe-ffi-wrapper.md b/src/exercises/day-3/safe-ffi-wrapper.md new file mode 100644 index 00000000..be755bf4 --- /dev/null +++ b/src/exercises/day-3/safe-ffi-wrapper.md @@ -0,0 +1,47 @@ +# Safe FFI Wrapper + +Rust has great support for calling functions through a _foreign function +interface_ (FFI). We will use this to build a safe wrapper the `glibc` functions +you would use from C to read the filenames of a directory. + +You will want to consult the manual pages: + +* [`opendir(3)`](https://man7.org/linux/man-pages/man3/opendir.3.html) +* [`readdir(3)`](https://man7.org/linux/man-pages/man3/readdir.3.html) +* [`closedir(3)`](https://man7.org/linux/man-pages/man3/closedir.3.html) + +You will also want to browse the [`std::ffi`] module, particular for [`CStr`] +and [`CString`] types which are used to hold NUL-terminated strings coming from +C. The [Nomicon] also has a very useful chapter about FFI. + +[`std::ffi`]: https://doc.rust-lang.org/std/ffi/ +[`CStr`]: https://doc.rust-lang.org/std/ffi/struct.CStr.html +[`CString`]: https://doc.rust-lang.org/std/ffi/struct.CString.html +[Nomicon]: https://doc.rust-lang.org/nomicon/ffi.html + +Copy the code below to and fill in the missing +functions and methods: + +```rust,should_panic +// TODO: remove this when you're done with your implementation. +#![allow(unused_imports, unused_variables, dead_code)] + +{{#include safe-ffi-wrapper.rs:ffi}} + +{{#include safe-ffi-wrapper.rs:DirectoryIterator}} + unimplemented!() + } +} + +{{#include safe-ffi-wrapper.rs:Iterator}} + unimplemented!() + } +} + +{{#include safe-ffi-wrapper.rs:Drop}} + unimplemented!() + } +} + +{{#include safe-ffi-wrapper.rs:main}} +``` diff --git a/src/exercises/day-3/safe-ffi-wrapper.rs b/src/exercises/day-3/safe-ffi-wrapper.rs new file mode 100644 index 00000000..5f3b50ab --- /dev/null +++ b/src/exercises/day-3/safe-ffi-wrapper.rs @@ -0,0 +1,110 @@ +// Copyright 2022 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// ANCHOR: ffi +mod ffi { + use std::os::raw::{c_char, c_int, c_long, c_ulong, c_ushort}; + + // Opaque type. See https://doc.rust-lang.org/nomicon/ffi.html. + #[repr(C)] + pub struct DIR { + _data: [u8; 0], + _marker: core::marker::PhantomData<(*mut u8, core::marker::PhantomPinned)>, + } + + // Layout as per readdir(3) and definitions in /usr/include/x86_64-linux-gnu. + #[repr(C)] + pub struct dirent { + pub d_ino: c_long, + pub d_off: c_ulong, + pub d_reclen: c_ushort, + pub d_type: c_char, + pub d_name: [c_char; 256], + } + + extern "C" { + pub fn opendir(s: *const c_char) -> *mut DIR; + pub fn readdir(s: *mut DIR) -> *const dirent; + pub fn closedir(s: *mut DIR) -> c_int; + } +} + +use std::ffi::{CStr, CString, OsStr, OsString}; +use std::os::unix::ffi::OsStrExt; + +#[derive(Debug)] +struct DirectoryIterator { + path: CString, + dir: *mut ffi::DIR, +} +// ANCHOR_END: ffi + +// ANCHOR: DirectoryIterator +impl DirectoryIterator { + fn new(path: &str) -> Result { + // Call opendir and return a Ok value if that worked, + // otherwise return Err with a message. + // ANCHOR_END: DirectoryIterator + let path = CString::new(path).map_err(|err| format!("Invalid path: {err}"))?; + // SAFETY: path.as_ptr() cannot be NULL. + let dir = unsafe { ffi::opendir(path.as_ptr()) }; + if dir.is_null() { + Err(format!("Could not open {:?}", path)) + } else { + Ok(DirectoryIterator { path, dir }) + } + } +} + +// ANCHOR: Iterator +impl Iterator for DirectoryIterator { + type Item = OsString; + fn next(&mut self) -> Option { + // Keep calling readdir until we get a NULL pointer back. + // ANCHOR_END: Iterator + // SAFETY: self.dir is never NULL. + let dirent = unsafe { ffi::readdir(self.dir) }; + if dirent.is_null() { + // We have reached the end of the directory. + return None; + } + // SAFETY: dirent is not NULL and dirent.d_name is NUL + // terminated. + let d_name = unsafe { CStr::from_ptr((*dirent).d_name.as_ptr()) }; + let os_str = OsStr::from_bytes(d_name.to_bytes()); + Some(os_str.to_owned()) + } +} + +// ANCHOR: Drop +impl Drop for DirectoryIterator { + fn drop(&mut self) { + // Call closedir as needed. + // ANCHOR_END: Drop + if !self.dir.is_null() { + // SAFETY: self.dir is not NULL. + if unsafe { ffi::closedir(self.dir) } != 0 { + panic!("Could not close {:?}", self.path); + } + } + } +} + +// ANCHOR: main +fn main() -> Result<(), String> { + let iter = DirectoryIterator::new(".")?; + println!("files: {:#?}", iter.collect::>()); + Ok(()) +} +// ANCHOR_END: main diff --git a/src/exercises/day-3/simple-gui.md b/src/exercises/day-3/simple-gui.md new file mode 100644 index 00000000..2d35ef15 --- /dev/null +++ b/src/exercises/day-3/simple-gui.md @@ -0,0 +1,91 @@ +# A Simple GUI Library + +Let us design a classical GUI library using our new knowledge of traits and +trait objects. + +We will have a number of widgets in our library: + +* `Window`: has a `title` and contains other widgets. +* `Button`: has a `label` and a callback function which is invoked when the + button is pressed. +* `Label`: has a `label`. + +The widgets will implement a `Widget` trait, see below. + +Copy the code below to , fill in the missing +`draw_into` methods so that you implement the `Widget` trait: + +```rust,should_panic +// TODO: remove this when you're done with your implementation. +#![allow(unused_imports, unused_variables, dead_code)] + +{{#include simple-gui.rs:setup}} + +{{#include simple-gui.rs:Label-width}} + unimplemented!() + } + +{{#include simple-gui.rs:Label-draw_into}} + unimplemented!() + } +} + +{{#include simple-gui.rs:Button-width}} + unimplemented!() + } + +{{#include simple-gui.rs:Button-draw_into}} + unimplemented!() + } +} + +{{#include simple-gui.rs:Window-width}} + unimplemented!() + } + +{{#include simple-gui.rs:Window-draw_into}} + unimplemented!() + } +} + +{{#include simple-gui.rs:main}} +``` + +The output of the above program can be something simple like this: + +```text +======== +Rust GUI Demo 1.23 +======== + +This is a small text GUI demo. + +| Click me! | +``` + +If you want to draw aligned text, you can use the +[fill/alignment](https://doc.rust-lang.org/std/fmt/index.html#fillalignment) +formatting operators. In particular, notice how you can pad with different +characters (here a `'/'`) and how you can control alignment: + +```rust,editable +fn main() { + let width = 10; + println!("left aligned: |{:/width$}|", "foo"); +} +``` + +Using such alignment tricks, you can for example produce output like this: + +```text ++--------------------------------+ +| Rust GUI Demo 1.23 | ++================================+ +| This is a small text GUI demo. | +| +-----------+ | +| | Click me! | | +| +-----------+ | ++--------------------------------+ +``` diff --git a/src/exercises/day-3/simple-gui.rs b/src/exercises/day-3/simple-gui.rs new file mode 100644 index 00000000..60ae67ff --- /dev/null +++ b/src/exercises/day-3/simple-gui.rs @@ -0,0 +1,160 @@ +// Copyright 2022 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// ANCHOR: setup +pub trait Widget { + /// Natural width of `self`. + fn width(&self) -> usize; + + /// Draw the widget into a buffer. + fn draw_into(&self, buffer: &mut dyn std::fmt::Write); + + /// Draw the widget on standard output. + fn draw(&self) { + let mut buffer = String::new(); + self.draw_into(&mut buffer); + println!("{}", &buffer); + } +} + +pub struct Label { + label: String, +} + +impl Label { + fn new(label: &str) -> Label { + Label { + label: label.to_owned(), + } + } +} + +pub struct Button { + label: Label, + callback: Box, +} + +impl Button { + fn new(label: &str, callback: Box) -> Button { + Button { + label: Label::new(label), + callback, + } + } +} + +pub struct Window { + title: String, + widgets: Vec>, +} + +impl Window { + fn new(title: &str) -> Window { + Window { + title: title.to_owned(), + widgets: Vec::new(), + } + } + + fn add_widget(&mut self, widget: Box) { + self.widgets.push(widget); + } +} + +// ANCHOR_END: setup + +// ANCHOR: Window-width +impl Widget for Window { + fn width(&self) -> usize { + // ANCHOR_END: Window-width + std::cmp::max( + self.title.chars().count(), + self.widgets.iter().map(|w| w.width()).max().unwrap_or(0), + ) + } + + // ANCHOR: Window-draw_into + fn draw_into(&self, buffer: &mut dyn std::fmt::Write) { + // ANCHOR_END: Window-draw_into + let mut inner = String::new(); + for widget in &self.widgets { + widget.draw_into(&mut inner); + } + + let window_width = self.width(); + + // TODO: after learning about error handling, you can change + // draw_into to return Result<(), std::fmt::Error>. Then use + // the ?-operator here instead of .unwrap(). + writeln!(buffer, "+-{:- usize { + // ANCHOR_END: Button-width + self.label.width() + 8 // add a bit of padding + } + + // ANCHOR: Button-draw_into + fn draw_into(&self, buffer: &mut dyn std::fmt::Write) { + // ANCHOR_END: Button-draw_into + let width = self.width(); + let mut label = String::new(); + self.label.draw_into(&mut label); + + writeln!(buffer, "+{:- usize { + // ANCHOR_END: Label-width + self.label + .lines() + .map(|line| line.chars().count()) + .max() + .unwrap_or(0) + } + + // ANCHOR: Label-draw_into + fn draw_into(&self, buffer: &mut dyn std::fmt::Write) { + // ANCHOR_END: Label-draw_into + writeln!(buffer, "{}", &self.label).unwrap(); + } +} + +// ANCHOR: main +fn main() { + let mut window = Window::new("Rust GUI Demo 1.23"); + window.add_widget(Box::new(Label::new("This is a small text GUI demo."))); + window.add_widget(Box::new(Button::new( + "Click me!", + Box::new(|| println!("You clicked the button!")), + ))); + window.draw(); +} +// ANCHOR_END: main diff --git a/src/exercises/day-3/solutions-afternoon.md b/src/exercises/day-3/solutions-afternoon.md new file mode 100644 index 00000000..c4863917 --- /dev/null +++ b/src/exercises/day-3/solutions-afternoon.md @@ -0,0 +1,9 @@ +# Day 3 Afternoon Exercises + +## Safe FFI Wrapper + +([back to exercise](safe-ffi-wrapper.md)) + +```rust +{{#include safe-ffi-wrapper.rs}} +``` diff --git a/src/exercises/day-3/solutions-morning.md b/src/exercises/day-3/solutions-morning.md new file mode 100644 index 00000000..069b0eeb --- /dev/null +++ b/src/exercises/day-3/solutions-morning.md @@ -0,0 +1,9 @@ +# Day 3 Morning Exercise + +## A Simple GUI Library + +([back to exercise](simple-gui.md)) + +```rust +{{#include simple-gui.rs}} +``` diff --git a/src/exercises/day-4/afternoon.md b/src/exercises/day-4/afternoon.md new file mode 100644 index 00000000..6d73a09e --- /dev/null +++ b/src/exercises/day-4/afternoon.md @@ -0,0 +1,8 @@ +# Exercises + +For the last exercise, we will look at one of the projects you work with. Let us +group up and do this together. Some suggestions: + +* Call your AIDL service with a client written in Rust. + +* Move a function from your project to Rust and call it. diff --git a/src/exercises/day-4/dining-philosophers.md b/src/exercises/day-4/dining-philosophers.md new file mode 100644 index 00000000..04387b36 --- /dev/null +++ b/src/exercises/day-4/dining-philosophers.md @@ -0,0 +1,37 @@ +# Dining Philosophers + +The dining philosophers problem is a classic problem in concurrency: + +> Five philosophers dine together at the same table. Each philosopher has their +> own place at the table. There is a fork between each plate. The dish served is +> a kind of spaghetti which has to be eaten with two forks. Each philosopher can +> only alternately think and eat. Moreover, a philosopher can only eat their +> spaghetti when they have both a left and right fork. Thus two forks will only +> be available when their two nearest neighbors are thinking, not eating. After +> an individual philosopher finishes eating, they will put down both forks. + +You will need a local [Cargo installation](../../cargo/running-locally.md) for +this exercise. Copy the code below to `src/main.rs` file, fill out the blanks, +and test that `cargo run` does not deadlock: + +```rust,compile_fail +{{#include dining-philosophers.rs:Philosopher}} + // left_fork: ... + // right_fork: ... + // thoughts: ... +} + +{{#include dining-philosophers.rs:Philosopher-think}} + +{{#include dining-philosophers.rs:Philosopher-eat}} + // Pick up forks... +{{#include dining-philosophers.rs:Philosopher-eat-end}} + // Create forks + + // Create philosophers + + // Make them think and eat + + // Output their thoughts +} +``` diff --git a/src/exercises/day-4/dining-philosophers.rs b/src/exercises/day-4/dining-philosophers.rs new file mode 100644 index 00000000..fc33d3dc --- /dev/null +++ b/src/exercises/day-4/dining-philosophers.rs @@ -0,0 +1,95 @@ +// Copyright 2022 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// ANCHOR: Philosopher +use std::sync::mpsc; +use std::sync::{Arc, Mutex}; +use std::thread; +use std::time::Duration; + +struct Fork; + +struct Philosopher { + name: String, + // ANCHOR_END: Philosopher + left_fork: Arc>, + right_fork: Arc>, + thoughts: mpsc::SyncSender, +} + +// ANCHOR: Philosopher-think +impl Philosopher { + fn think(&self) { + self.thoughts + .send(format!("Eureka! {} has a new idea!", &self.name)) + .unwrap(); + } + // ANCHOR_END: Philosopher-think + + // ANCHOR: Philosopher-eat + fn eat(&self) { + // ANCHOR_END: Philosopher-eat + println!("{} is trying to eat", &self.name); + let left = self.left_fork.lock().unwrap(); + let right = self.right_fork.lock().unwrap(); + + // ANCHOR: Philosopher-eat-end + println!("{} is eating...", &self.name); + thread::sleep(Duration::from_millis(10)); + } +} + +static PHILOSOPHERS: &[&str] = + &["Socrates", "Plato", "Aristotle", "Thales", "Pythagoras"]; + +fn main() { + // ANCHOR_END: Philosopher-eat-end + let (tx, rx) = mpsc::sync_channel(10); + + let forks = (0..PHILOSOPHERS.len()) + .map(|_| Arc::new(Mutex::new(Fork))) + .collect::>(); + + for i in 0..forks.len() { + let tx = tx.clone(); + let mut left_fork = forks[i].clone(); + let mut right_fork = forks[(i + 1) % forks.len()].clone(); + + // To avoid a deadlock, we have to break the symmetry + // somewhere. This will swap the forks without deinitializing + // either of them. + if i == forks.len() - 1 { + std::mem::swap(&mut left_fork, &mut right_fork); + } + + let philosopher = Philosopher { + name: PHILOSOPHERS[i].to_string(), + thoughts: tx, + left_fork, + right_fork, + }; + + thread::spawn(move || { + for _ in 0..100 { + philosopher.eat(); + philosopher.think(); + } + }); + } + + drop(tx); + for thought in rx { + println!("{}", thought); + } +} diff --git a/src/exercises/day-4/link-checker.md b/src/exercises/day-4/link-checker.md new file mode 100644 index 00000000..6f95e7d2 --- /dev/null +++ b/src/exercises/day-4/link-checker.md @@ -0,0 +1,80 @@ +# Multi-threaded Link Checker + +Let us use our new knowledge to create a multi-threaded link checker. It should +start at a webpage and check that links on the page are valid. It should +recursively check other pages on the same domain and keep doing this until all +pages have been validated. + +For this, you will need an HTTP client such as [`reqwest`][1]. Create a new +Cargo project and `reqwest` it as a dependency with: + +```shell +$ cargo new link-checker +$ cd link-checker +$ cargo add --features blocking reqwest +``` + +> If `cargo add` fails with `error: no such subcommand`, then please edit the +> `Cargo.toml` file by hand. Add the dependencies listed below. + +You will also need a way to find links. We can use [`scraper`][2] for that: + +```shell +$ cargo add scraper +``` + +Finally, we'll need some way of handling errors. We [`thiserror`][3] for that: + +```shell +$ cargo add thiserror +``` + +The `cargo add` calls will update the `Cargo.toml` file to look like this: + +```toml +[dependencies] +reqwest = { version = "0.11.12", features = ["blocking"] } +scraper = "0.13.0" +thiserror = "1.0.37" +``` + +You can now download the start page. Try with a small site such as +`https://www.google.org/`. + +Your `src/main.rs` file should look something like this: + +```rust,compile_fail +{{#include link-checker.rs:setup}} + +{{#include link-checker.rs:extract_links}} + +fn main() { + let start_url = Url::parse("https://www.google.org").unwrap(); + let response = get(start_url).unwrap(); + match extract_links(response) { + Ok(links) => println!("Links: {links:#?}"), + Err(err) => println!("Could not extract links: {err:#}"), + } +} +``` + +Run the code in `src/main.rs` with + +```shell +$ cargo run +``` + +> If `cargo run` fails with an error mentioning OpenSSL, ensure that you have +> the OpenSSL headers installed by running `sudo apt install libssl-dev`. + +## Tasks + +* Use threads to check the links in parallel: send the URLs to be checked to a + channel and let a few threads check the URLs in parallel. +* Extend this to recursively extract links from all pages on the + `www.google.org` domain. Put an upper limit of 100 pages or so so that you + don't end up being blocked by the site. + +[1]: https://docs.rs/reqwest/ +[2]: https://docs.rs/scraper/ +[3]: https://docs.rs/thiserror/ diff --git a/src/exercises/day-4/link-checker.rs b/src/exercises/day-4/link-checker.rs new file mode 100644 index 00000000..114187ed --- /dev/null +++ b/src/exercises/day-4/link-checker.rs @@ -0,0 +1,89 @@ +// Copyright 2022 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// ANCHOR: setup +use reqwest::blocking::{get, Response}; +use reqwest::Url; +use scraper::{Html, Selector}; +use thiserror::Error; + +#[derive(Error, Debug)] +enum Error { + #[error("request error: {0}")] + ReqwestError(#[from] reqwest::Error), +} +// ANCHOR_END: setup + +// ANCHOR: extract_links +fn extract_links(response: Response) -> Result, Error> { + let base_url = response.url().to_owned(); + let document = response.text()?; + let html = Html::parse_document(&document); + let selector = Selector::parse("a").unwrap(); + + let mut valid_urls = Vec::new(); + for element in html.select(&selector) { + if let Some(href) = element.value().attr("href") { + match base_url.join(href) { + Ok(url) => valid_urls.push(url), + Err(err) => { + println!("On {base_url}: could not parse {href:?}: {err} (ignored)",); + } + } + } + } + + Ok(valid_urls) +} +// ANCHOR_END: extract_links + +fn check_links(url: Url) -> Result, Error> { + println!("Checking {url}"); + + let response = get(url.to_owned())?; + + if !response.status().is_success() { + return Ok(vec![url.to_owned()]); + } + + let links = extract_links(response)?; + for link in &links { + println!("{link}, {:?}", link.domain()); + } + + let mut failed_links = Vec::new(); + for link in links { + if link.domain() != url.domain() { + println!("Checking external link: {link}"); + let response = get(link.clone())?; + if !response.status().is_success() { + println!("Error on {url}: {link} failed: {}", response.status()); + failed_links.push(link); + } + } else { + println!("Checking link in same domain: {link}"); + failed_links.extend(check_links(link)?) + } + } + + Ok(failed_links) +} + +fn main() { + let start_url = Url::parse("https://www.google.org").unwrap(); + match check_links(start_url) { + Ok(links) => println!("Links: {links:#?}"), + Err(err) => println!("Could not extract links: {err:#}"), + } +} diff --git a/src/exercises/day-4/morning.md b/src/exercises/day-4/morning.md new file mode 100644 index 00000000..c289ded0 --- /dev/null +++ b/src/exercises/day-4/morning.md @@ -0,0 +1,8 @@ +# Exercises + +Let us practice our new concurrency skills with + +* Dining philosophers: a classic problems in concurrency. + +* Multi-threaded link checker: a larger project where you'll use Cargo to + download dependencies and then check links in parallel. diff --git a/src/exercises/day-4/solutions-morning.md b/src/exercises/day-4/solutions-morning.md new file mode 100644 index 00000000..e495b04d --- /dev/null +++ b/src/exercises/day-4/solutions-morning.md @@ -0,0 +1,10 @@ +# Day 4 Morning Exercise + +## Dining Philosophers + +([back to exercise](dining-philosophers.md)) + +```rust +{{#include dining-philosophers.rs}} +``` + diff --git a/src/exercises/solutions.md b/src/exercises/solutions.md new file mode 100644 index 00000000..333eb92d --- /dev/null +++ b/src/exercises/solutions.md @@ -0,0 +1,12 @@ +# Solutions + +You will find solutions to the exercises on the following pages. + +Feel free to ask questions about the solutions [on +GitHub](https://github.com/google/comprehensive-rust/discussions). Let us know +if you have a different or better solution than what is presented here. + + +> **Note:** Please ignore the `// ANCHOR: label` and `// ANCHOR_END: label` +> comments you see in the solutions. They are there to make it possible to +> re-use parts of the solutions as the exercises. diff --git a/src/generics.md b/src/generics.md new file mode 100644 index 00000000..61f7925f --- /dev/null +++ b/src/generics.md @@ -0,0 +1,4 @@ +# Generics + +Rust support generics, which lets you abstract an algorithm (such as sorting) +over the types used in the algorithm. diff --git a/src/generics/closures.md b/src/generics/closures.md new file mode 100644 index 00000000..248aa405 --- /dev/null +++ b/src/generics/closures.md @@ -0,0 +1,21 @@ +# Closures + +Closures or lambda expressions have types which cannot be named. However, they +implement special [`Fn`](https://doc.rust-lang.org/std/ops/trait.Fn.html), +[`FnMut`](https://doc.rust-lang.org/std/ops/trait.FnMut.html), and +[`FnOnce`](https://doc.rust-lang.org/std/ops/trait.FnOnce.html) traits: + +```rust,editable +fn apply_with_log(func: impl FnOnce(i32) -> i32, input: i32) -> i32 { + println!("Calling function on {input}"); + func(input) +} + +fn main() { + let add_3 = |x| x + 3; + let mul_5 = |x| x * 5; + + println!("add_3: {}", apply_with_log(add_3, 10)); + println!("mul_5: {}", apply_with_log(mul_5, 20)); +} +``` diff --git a/src/generics/data-types.md b/src/generics/data-types.md new file mode 100644 index 00000000..03587d19 --- /dev/null +++ b/src/generics/data-types.md @@ -0,0 +1,17 @@ +# Generic Data Types + +You can use generics to abstract over the concrete field type: + +```rust,editable +#[derive(Debug)] +struct Point { + x: T, + y: T, +} + +fn main() { + let integer = Point { x: 5, y: 10 }; + let float = Point { x: 1.0, y: 4.0 }; + println!("{integer:?} and {float:?}"); +} +``` diff --git a/src/generics/impl-trait.md b/src/generics/impl-trait.md new file mode 100644 index 00000000..58afd6a5 --- /dev/null +++ b/src/generics/impl-trait.md @@ -0,0 +1,20 @@ +# `impl Trait` + +Similar to trait bounds, an `impl Trait` syntax can be used in function +arguments and return values: + +```rust,editable +use std::fmt::Display; + +fn get_x(name: impl Display) -> impl Display { + format!("Hello {name}") +} + +fn main() { + let x = get_x("foo"); + println!("{x}"); +} +``` + +* `impl Trait` cannot be used with the `::<>` turbo fish syntax. +* `impl Trait` allows you to work with types which you cannot name. diff --git a/src/generics/methods.md b/src/generics/methods.md new file mode 100644 index 00000000..9a901b57 --- /dev/null +++ b/src/generics/methods.md @@ -0,0 +1,21 @@ +# Generic Methods + +You can declare a generic type on your `impl` block: + +```rust,editable +#[derive(Debug)] +struct Point(T, T); + +impl Point { + fn x(&self) -> &T { + &self.0 // + 10 + } + + // fn set_x(&mut self, x: T) +} + +fn main() { + let p = Point(5, 10); + println!("p.x = {}", p.x()); +} +``` diff --git a/src/generics/monomorphization.md b/src/generics/monomorphization.md new file mode 100644 index 00000000..28d106c4 --- /dev/null +++ b/src/generics/monomorphization.md @@ -0,0 +1,32 @@ +# Monomorphization + +Generic code is turned into non-generic code based on the call sites: + +```rust,editable +fn main() { + let integer = Some(5); + let float = Some(5.0); +} +``` + +behaves as if you wrote + +```rust,editable +enum Option_i32 { + Some(i32), + None, +} + +enum Option_f64 { + Some(f64), + None, +} + +fn main() { + let integer = Option_i32::Some(5); + let float = Option_f64::Some(5.0); +} +``` + +This is a zero-cost abstraction: you get exactly the same result as if you had +hand-coded the data structures without the abstraction. diff --git a/src/generics/trait-bounds.md b/src/generics/trait-bounds.md new file mode 100644 index 00000000..0439b710 --- /dev/null +++ b/src/generics/trait-bounds.md @@ -0,0 +1,17 @@ +# Trait Bounds + +When working with generics, you often want to limit the types. You can do this +with `T: Trait` or `impl Trait`: + +```rust,editable +fn duplicate(a: T) -> (T, T) { + (a.clone(), a.clone()) +} + +// struct NotClonable; + +fn main() { + let foo = String::from("foo"); + let pair = duplicate(foo); +} +``` diff --git a/src/generics/trait-objects.md b/src/generics/trait-objects.md new file mode 100644 index 00000000..140fab6f --- /dev/null +++ b/src/generics/trait-objects.md @@ -0,0 +1,86 @@ +# Trait Objects + +We've seen how a function can take arguments which implement a trait: + +```rust,editable +use std::fmt::Display; + +fn print(x: T) { + println!("Your value: {}", x); +} + +fn main() { + print(123); + print("Hello"); +} +``` + +However, how can we store a collection of mixed types which implement `Display`? + +```rust,editable,compile_fail +fn main() { + let xs = vec![123, "Hello"]; +} +``` + +For this, we need _trait objects_: + +```rust,editable +use std::fmt::Display; + +fn main() { + let xs: Vec> = vec![Box::new(123), Box::new("Hello")]; + for x in xs { + println!("x: {x}"); + } +} +``` + +Memory layout after allocating `xs`: + +```bob + Stack Heap +.- - - - - - - - - - - - - -. .- - - - - - - - - - - - - - - - - - - - - - - -. +: : : : +: xs : : : +: +-----------+-------+ : : +-----+-----+ : +: | ptr | o---+---+-----+-->| o o | o o | : +: | len | 2 | : : +-|-|-+-|-|-+ : +: | capacity | 2 | : : | | | | +----+----+----+----+----+ : +: +-----------+-------+ : : | | | '-->| H | e | l | l | o | : +: : : | | | +----+----+----+----+----+ : +`- - - - - - - - - - - - - -' : | | | : + : | | | +-------------------------+ : + : | | '---->| "::fmt" | : + : | | +-------------------------+ : + : | | : + : | | +-------------------------+ : + : | '-->| "::fmt" | : + : | +-------------------------+ : + : | : + : | +----+----+----+----+ : + : '---->| 7b | 00 | 00 | 00 | : + : +----+----+----+----+ : + : : + : : + '- - - - - - - - - - - - - - - - - - - - - - - -' +``` + +Similarly, you need a trait object if you want to return different values +implementing a trait: + +```rust,editable +fn numbers(n: i32) -> Box> { + if n > 0 { + Box::new(0..n) + } else { + Box::new((n..0).rev()) + } +} + +fn main() { + println!("{:?}", numbers(-5).collect::>()); + println!("{:?}", numbers(5).collect::>()); +} + +``` diff --git a/src/hello-world.md b/src/hello-world.md new file mode 100644 index 00000000..8dfbead5 --- /dev/null +++ b/src/hello-world.md @@ -0,0 +1,18 @@ +# Hello World! + +Let us jump into the simplest possible Rust program, a classic Hello World +program: + +```rust +fn main() { + println!("Hello 🌍!"); +} +``` + +What you see: + +* Functions are introduced with `fn`. +* Blocks are delimited by curly braces like in C and C++. +* The `main` function is the entry point of the program. +* Rust has hygenic macros, `println!` is an example of this. +* Rust strings can contain Unicode characters, such as emoji. diff --git a/src/hello-world/small-example.md b/src/hello-world/small-example.md new file mode 100644 index 00000000..b1df4b1d --- /dev/null +++ b/src/hello-world/small-example.md @@ -0,0 +1,20 @@ +# Small Example + +Here is a small example program in Rust: + +```rust,editable +fn main() { // Program entry point + let mut x: i32 = 6; // Mutable variable binding + print!("{x}"); // Macro for printing, like printf + while x != 1 { // No parenthesis around expression + if x % 2 == 0 { // Math like in other languages + x = x / 2; + } else { + x = 3 * x + 1; + } + print!(" -> {x}"); + } + println!(); +} +``` + diff --git a/src/memory-management.md b/src/memory-management.md new file mode 100644 index 00000000..a1a8e999 --- /dev/null +++ b/src/memory-management.md @@ -0,0 +1,15 @@ +# Memory Management + +Traditionally, languages have fallen into two broad categories: + +* Full control via manual memory management: C, C++, Pascal, ... +* Full safety via automatic memory management at runtime: Java, Python, Go, Haskell, ... + +Rust offers a new mix: + +> Full control *and* safety via compile time enforcement of correct memory +> management. + +It does this with an explicit ownership concept. + +First, let's refresh how memory management works. diff --git a/src/memory-management/comparison.md b/src/memory-management/comparison.md new file mode 100644 index 00000000..91c00d9f --- /dev/null +++ b/src/memory-management/comparison.md @@ -0,0 +1,34 @@ +# Comparison + +Here is a rough comparison of the memory management techniques. + +## Pros of Different Memory Management Techniques + +* Manual like C + * No runtime overhead +* Automatic like Java + * Fully automatic + * Safe and correct +* Scope-based like C++ + * Partially automatic + * No runtime overhead +* Compiler-enforced scope-based like Rust + * Enforced by compiler + * No runtime overhead + * Safe and correct + +## Cons of Different Memory Management Techniques + +* Manual like C: + * Use-after-free + * Double-frees + * Memory leaks +* Automatic like Java + * Garbage collection pauses + * Destructors delays +* Scope-based like C++ + * Complex, opt-in by programmer + * Potential for use-after-free +* Compiler-enforced and scope-based like Rust + * Some upfront complexity + * Can reject valid programs diff --git a/src/memory-management/garbage-collection.md b/src/memory-management/garbage-collection.md new file mode 100644 index 00000000..33c5d1b8 --- /dev/null +++ b/src/memory-management/garbage-collection.md @@ -0,0 +1,17 @@ +# Automatic Memory Management + +An alternative to manual and scope-based memory management is automatic memory +management: + +* The programmer never calls allocates or deallocates memory explicitly. +* A garbage collector finds unused memory and deallocates it for the programmer. + +## Java Example + +The `person` object is not deallocated after `sayHello` returns: + +```java +void sayHello(Person person) { + System.out.println("Hello " + person.getName()); +} +``` diff --git a/src/memory-management/manual.md b/src/memory-management/manual.md new file mode 100644 index 00000000..ad2288ec --- /dev/null +++ b/src/memory-management/manual.md @@ -0,0 +1,20 @@ +# Manual Memory Management + +You allocate and deallocate heap memory yourself. + +## C Example + +You must call `free` on every pointer you allocate with `malloc`: + +```c +void foo(size_t n) { + int[] int_array = (int*)malloc(n * sizeof(int)); + // + // ... lots of code + // + free(int_array); +} +``` + +Memory is leaked if the function returns early between `malloc` and `free`: the +pointer is lost and we cannot deallocate the memory. diff --git a/src/memory-management/rust.md b/src/memory-management/rust.md new file mode 100644 index 00000000..e75f8a4b --- /dev/null +++ b/src/memory-management/rust.md @@ -0,0 +1,9 @@ +# Memory Management in Rust + +Memory management in Rust is a mix: + +* Safe and correct like Java, but without a garbage collector. +* Scope-based like C++, but the compiler enforces full adherence. +* Has no runtime overhead like in C and C++. + +It achieves this by modeling _ownership_ explicitly. diff --git a/src/memory-management/scope-based.md b/src/memory-management/scope-based.md new file mode 100644 index 00000000..3cbf34b5 --- /dev/null +++ b/src/memory-management/scope-based.md @@ -0,0 +1,30 @@ +# Scope-Based Memory Management + +Constructors and destructors let you hook into the lifetime of an object. + +By wrapping a pointer in an object, you can free memory when the object is +destroyed. The compiler guarantees that this happens, even if an exception is +raised. + +This is often called _resource acquisition is initialization_ (RAII) and gives +you smart pointers. + +## C++ Example + +```c++ +void say_hello(std::unique_ptr person) { + std::cout << "Hello " << person->name << std::endl; +} +``` + +* The `std::unique_ptr` object is allocated on the stack, and points to + memory allocated on the heap. +* At the end of `say_hello`, the `std::unique_ptr` destructor will run. +* The destructor frees the `Person` object it points to. + +Special move constructors are used when passing ownership to a function: + +```c++ +std::unique_ptr person = find_person("Carla"); +say_hello(std::move(person)); +``` diff --git a/src/memory-management/stack-vs-heap.md b/src/memory-management/stack-vs-heap.md new file mode 100644 index 00000000..59c93146 --- /dev/null +++ b/src/memory-management/stack-vs-heap.md @@ -0,0 +1,12 @@ +# The Stack vs The Heap + +* Stack: Continuous area of memory for local variables. + * Values have fixed sizes known at compile time. + * Extremely fast: just move a stack pointer. + * Easy to manage: follows function calls. + * Great memory locality. + +* Heap: Storage of values outside of function calls. + * Values have dynamic sizes determined at runtime. + * Slightly slower than the stack: some book-keeping needed. + * No guarantee of memory locality. diff --git a/src/memory-management/stack.md b/src/memory-management/stack.md new file mode 100644 index 00000000..b1b1dbd0 --- /dev/null +++ b/src/memory-management/stack.md @@ -0,0 +1,24 @@ +# Stack Memory + +Creating a `String` puts fixed-sized data on the stack and dynamically sized +data on the heap: + +```rust,editable +fn main() { + let s1 = String::from("Hello"); +} +``` + +```bob + Stack Heap +.- - - - - - - - - - - - - -. .- - - - - - - - - - - - - - - -. +: : : : +: s1 : : : +: +-----------+-------+ : : +----+----+----+----+----+ : +: | ptr | o---+---+-----+-->| H | e | l | l | o | : +: | len | 5 | : : +----+----+----+----+----+ : +: | capacity | 5 | : : : +: +-----------+-------+ : : : +: : `- - - - - - - - - - - - - - - -' +`- - - - - - - - - - - - - -' +``` diff --git a/src/methods.md b/src/methods.md new file mode 100644 index 00000000..ae599dd6 --- /dev/null +++ b/src/methods.md @@ -0,0 +1,27 @@ +# Methods + +Rust allows you to associate functions with your new types. You do this with an +`impl` block: + +```rust,editable +#[derive(Debug)] +struct Person { + name: String, + age: u8, +} + +impl Person { + fn say_hello(&self) { + println!("Hello, my name is {}", self.name); + } +} + +fn main() { + let peter = Person { + name: String::from("Peter"), + age: 27, + }; + peter.say_hello(); +} +``` + diff --git a/src/methods/example.md b/src/methods/example.md new file mode 100644 index 00000000..aea2c9cf --- /dev/null +++ b/src/methods/example.md @@ -0,0 +1,41 @@ +# Example + +```rust,editable +#[derive(Debug)] +struct Race { + name: String, + laps: Vec, +} + +impl Race { + fn new(name: &str) -> Race { // No receiver, a static method + Race { name: String::from(name), laps: Vec::new() } + } + + fn add_lap(&mut self, lap: i32) { // Exclusive borrowed read-write access to self + self.laps.push(lap); + } + + fn print_laps(&self) { // Shared and read-only borrowed access to self + println!("Recorded {} laps for {}:", self.laps.len(), self.name); + for (idx, lap) in self.laps.iter().enumerate() { + println!("Lap {idx}: {lap} sec"); + } + } + + fn finish(self) { // Exclusive ownership of self + let total = self.laps.iter().sum::(); + println!("Race {} is finished, total lap time: {}", self.name, total); + } +} + +fn main() { + let mut race = Race::new("Monaco Grand Prix"); + race.add_lap(70); + race.add_lap(68); + race.print_laps(); + race.add_lap(71); + race.print_laps(); + race.finish(); +} +``` diff --git a/src/methods/receiver.md b/src/methods/receiver.md new file mode 100644 index 00000000..113bef16 --- /dev/null +++ b/src/methods/receiver.md @@ -0,0 +1,14 @@ +# Method Receiver + +The `&self` above indicates that the method borrows the object immutably. There +are other possible receivers for a method: + +* `&self`: borrows the object from the caller using a shared and immutable + reference. The object can be used again afterwards. +* `&mut self`: borrows the object from the caller using a unique and mutable + reference. The object can be used again afterwards. +* `self`: takes ownership of the object and moves it away from the caller. The + method becomes the owner of the object and will drop (deallocate) it at the + end of the scope. +* No receiver: this becomes a static method on the struct. Typically used to + create constructors which are called `new` by convention. diff --git a/src/modules.md b/src/modules.md new file mode 100644 index 00000000..58324972 --- /dev/null +++ b/src/modules.md @@ -0,0 +1,24 @@ +# Modules + +We have seen how `impl` blocks lets us namespace functions to a type. + +Similarly, `mod` lets us namespace types and functions: + +```rust,editable +mod foo { + pub fn do_something() { + println!("In the foo module"); + } +} + +mod bar { + pub fn do_something() { + println!("In the bar module"); + } +} + +fn main() { + foo::do_something(); + bar::do_something(); +} +``` diff --git a/src/modules/filesystem.md b/src/modules/filesystem.md new file mode 100644 index 00000000..649f07b9 --- /dev/null +++ b/src/modules/filesystem.md @@ -0,0 +1,22 @@ +# Filesystem Hierarchy + +The module content can be omitted: + +```rust,editable,compile_fail +mod garden; +``` + +The `garden` module content is found at: + +* `src/garden.rs` (modern Rust 2018 style) +* `src/garden/mod.rs` (older Rust 2015 style) + +Similarly, a `garden::vegetables` module can be found at: + +* `src/garden/vegetables.rs` (modern Rust 2018 style) +* `src/garden/vegetables/mod.rs` (older Rust 2015 style) + +The `crate` root is in: + +* `src/lib.rs` (for a library crate) +* `src/main.rs` (for a binary crate) diff --git a/src/modules/paths.md b/src/modules/paths.md new file mode 100644 index 00000000..2a6f3d0f --- /dev/null +++ b/src/modules/paths.md @@ -0,0 +1,11 @@ +# Paths + +Paths are resolved as follows: + +1. As a relative path: + * `foo` or `self::foo` refers to `foo` in the current module, + * `super::foo` refers to `foo` in the parent module. + +2. As an absolute path: + * `crate::foo` refers to `foo` in the root of the current crate, + * `bar::foo` refers to `foo` in the `bar` crate. diff --git a/src/modules/visibility.md b/src/modules/visibility.md new file mode 100644 index 00000000..ae22ae75 --- /dev/null +++ b/src/modules/visibility.md @@ -0,0 +1,33 @@ +# Visibility + +Module are a privacy boundary: + +* Module items are private by default (hides implementation details). +* Parent and sibling items are always visible. + +```rust,editable +mod outer { + fn private() { + println!("outer::private"); + } + + pub fn public() { + println!("outer::public"); + } + + mod inner { + fn private() { + println!("outer::inner::private"); + } + + pub fn public() { + println!("outer::inner::public"); + super::private(); + } + } +} + +fn main() { + outer::public(); +} +``` diff --git a/src/other-resources.md b/src/other-resources.md new file mode 100644 index 00000000..f219995e --- /dev/null +++ b/src/other-resources.md @@ -0,0 +1,60 @@ +# Other Rust Resources + +The Rust community has created a wealth of high-quality and free resources +online. + +## Offical Documentation + +The Rust project hosts many resources. These cover Rust in general: + +* [The Rust Programming Language](https://doc.rust-lang.org/book/): the + canonical free book about Rust. Covers the language in detail and includes a + few projects for people to build. +* [Rust By Example](https://doc.rust-lang.org/rust-by-example/): covers the Rust + syntax via a series of examples which showcase different constructs. Sometimes + includes small exercises where you are asked to expand on the code in the + examples. +* [Rust Standard Library](https://doc.rust-lang.org/std/): full documentation of + the standard library for Rust. +* [The Rust Reference](https://doc.rust-lang.org/reference/): an incomplete book + which describes the Rust grammar and memory model. + +More specialized guides hosted on the official Rust site: + +* [The Rustonomicon](https://doc.rust-lang.org/nomicon/): covers unsafe Rust, + including working with raw pointers and interfacing with other languages + (FFI). +* [Asynchronous Programming in Rust](https://rust-lang.github.io/async-book/): + covers the new asynchronous programming model which was introduced after the + Rust Book was written. +* [The Embedded Rust Book](https://doc.rust-lang.org/stable/embedded-book/): an + introduction to using Rust on embedded devices without an operating system. + +## Unofficial Learning Material + +A small selection of other guides and tutorial for Rust: + +* [Learn Rust the Dangerous Way](http://cliffle.com/p/dangerust/): covers Rust + from the perspective of low-level C programmers. +* [Rust for Embedded C + Programmers](https://docs.opentitan.org/doc/ug/rust_for_c/): covers Rust from + the perspective of developers who write firmware in C. +* [Rust for professionals](https://overexact.com/rust-for-professionals/): + covers the syntax of Rust using side-by-side comparisons with other languages + such as C, C++, Java, JavaScript, and Python. +* [Rust on Exercism](https://exercism.org/tracks/rust): 100+ exercises to help + you learn Rust. +* [Ferrous Teaching + Material](https://ferrous-systems.github.io/teaching-material/index.html): a + series of small presentations covering both basic and advanced part of the + Rust language. Other topics such as WebAssembly, and async/await are also + covered. +* [Beginner's Series to + Rust](https://docs.microsoft.com/en-us/shows/beginners-series-to-rust/) and + [Take your first steps with + Rust](https://docs.microsoft.com/en-us/learn/paths/rust-first-steps/): two + Rust guides aimed at new developers. The first is a set of 35 videos and the + second is a set of 11 modules which covers Rust syntax and basic constructs. + +Please see the [Little Book of Rust Books](https://lborb.github.io/book/) for +even more Rust books. diff --git a/src/ownership.md b/src/ownership.md new file mode 100644 index 00000000..db783014 --- /dev/null +++ b/src/ownership.md @@ -0,0 +1,20 @@ +# Ownership + +All variable bindings have a _scope_ where they are valid and it is an error to +use a variable outside its scope: + +```rust,editable,compile_fail +struct Point(i32, i32); + +fn main() { + { + let p = Point(3, 4); + println!("x: {}", p.0); + } + println!("y: {}", p.1); +} +``` + +* At the end of the scope, the variable is _dropped_ and the data is freed. +* A destructor can run here to free up resources. +* We say that the variable _owns_ the value. diff --git a/src/ownership/borrowing.md b/src/ownership/borrowing.md new file mode 100644 index 00000000..36cf27c0 --- /dev/null +++ b/src/ownership/borrowing.md @@ -0,0 +1,23 @@ +# Borrowing + +Instead of transferring ownership when calling a function, you can let a +function _borrow_ the value: + +```rust,editable +#[derive(Debug)] +struct Point(i32, i32); + +fn add(p1: &Point, p2: &Point) -> Point { + Point(p1.0 + p2.0, p1.1 + p2.1) +} + +fn main() { + let p1 = Point(3, 4); + let p2 = Point(10, 20); + let p3 = add(&p1, &p2); + println!("{p1:?} + {p2:?} = {p3:?}"); +} +``` + +* The `add` function _borrows_ two points and returns a new point. +* The caller retains ownership of the inputs. diff --git a/src/ownership/copy-clone.md b/src/ownership/copy-clone.md new file mode 100644 index 00000000..b6cfa6d5 --- /dev/null +++ b/src/ownership/copy-clone.md @@ -0,0 +1,31 @@ +# Copying and Cloning + +While move semantics is the default, certain types are copied by default: + +```rust,editable +fn main() { + let x = 42; + let y = x; + println!("x: {x}"); + println!("y: {y}"); +} +``` + +These types implement the `Copy` trait. + +You can opt-in your own types to use copy semantics: + +```rust,editable +#[derive(Copy, Clone, Debug)] +struct Point(i32, i32); + +fn main() { + let p1 = Point(3, 4); + let p2 = p1; + println!("p1: {p1:?}"); + println!("p2: {p2:?}"); +} +``` + +* After the assignment, both `p1` and `p2` own their own data. +* We can also use `p1.clone()` to explicitly copy the data. diff --git a/src/ownership/double-free-modern-cpp.md b/src/ownership/double-free-modern-cpp.md new file mode 100644 index 00000000..d860edb0 --- /dev/null +++ b/src/ownership/double-free-modern-cpp.md @@ -0,0 +1,51 @@ +# Double Frees in Modern C++ + +Modern C++ solves this differently: + +```c++ +std::string s1 = "Cpp"; +std::string s2 = s1; // Duplicate the data in s1. +``` + +* The heap data from `s1` is duplicated and `s2` gets its own independent copy. +* When `s1` and `s2` go out of scope, they each free their own memory. + +Before copy-assignment: + + +```bob + Stack Heap +.- - - - - - - - - - - - - -. .- - - - - - - - - - - -. +: : : : +: s1 : : : +: +-----------+-------+ : : +----+----+----+ : +: | ptr | o---+---+--+--+-->| C | p | p | : +: | len | 3 | : : +----+----+----+ : +: | capacity | 3 | : : : +: +-----------+-------+ : : : +: : `- - - - - - - - - - - -' +`- - - - - - - - - - - - - -' +``` + +After copy-assignment: + +```bob + Stack Heap +.- - - - - - - - - - - - - -. .- - - - - - - - - - - -. +: : : : +: s1 : : : +: +-----------+-------+ : : +----+----+----+ : +: | ptr | o---+---+--+--+-->| C | p | p | : +: | len | 3 | : : +----+----+----+ : +: | capacity | 3 | : : : +: +-----------+-------+ : : : +: : : : +: s2 : : : +: +-----------+-------+ : : +----+----+----+ : +: | ptr | o---+---+-----+-->| C | p | p | : +: | len | 3 | : : +----+----+----+ : +: | capacity | 3 | : : : +: +-----------+-------+ : : : +: : `- - - - - - - - - - - -' +`- - - - - - - - - - - - - -' +``` diff --git a/src/ownership/lifetimes-data-structures.md b/src/ownership/lifetimes-data-structures.md new file mode 100644 index 00000000..6ce82713 --- /dev/null +++ b/src/ownership/lifetimes-data-structures.md @@ -0,0 +1,22 @@ +# Lifetimes in Data Structures + +If a data type stores borrowed data, it must be annotated with a lifetime: + +```rust,editable +#[derive(Debug)] +struct Highlight<'doc>(&'doc str); + +fn erase(text: String) { + println!("Bye {text}!"); +} + +fn main() { + let text = String::from("The quick brown fox jumps over the lazy dog."); + let fox = Highlight(&text[4..19]); + let dog = Highlight(&text[35..43]); + // erase(text); + println!("{fox:?}"); + println!("{dog:?}"); +} +``` + diff --git a/src/ownership/lifetimes-function-calls.md b/src/ownership/lifetimes-function-calls.md new file mode 100644 index 00000000..71971650 --- /dev/null +++ b/src/ownership/lifetimes-function-calls.md @@ -0,0 +1,25 @@ +# Lifetimes in Function Calls + +In addition to borrow its arguments, a function can return a borrowed value: + +```rust,editable +#[derive(Debug)] +struct Point(i32, i32); + +fn left_most<'a>(p1: &'a Point, p2: &'a Point) -> &'a Point { + if p1.0 < p2.0 { p1 } else { p2 } +} + +fn main() { + let p1: Point = Point(10, 10); + let p2: Point = Point(20, 20); // Put into different scope + let p3: &Point = left_most(&p1, &p2); + println!("left-most point: {:?}", p3); +} +``` + +* `'a` is a generic parameter, it is inferred by the compiler. +* Lifetimes start with `'` and `'a` is a typical default name. +* Read `&'a Point` as "a borrowed `Point` which is valid for at least the + lifetime `a`". + * The _at least_ part is important when parameters are in different scopes. diff --git a/src/ownership/lifetimes.md b/src/ownership/lifetimes.md new file mode 100644 index 00000000..cd78040a --- /dev/null +++ b/src/ownership/lifetimes.md @@ -0,0 +1,12 @@ +# Lifetimes + +A borrowed value has a _lifetime_: + +* The lifetime can be elided: `add(p1: &Point, p2: &Point) -> Point`. +* Lifetimes can also be explicit: `&'a Point`, `&'document str`. +* Read `&'a Point` as "a borrowed `Point` which is valid for at least the + lifetime `a`". +* Lifetimes are always inferred by the compiler: you cannot assign a lifetime + yourself. + * Lifetime annotations create constraints; the compiler verifies that there is + a valid solution. diff --git a/src/ownership/move-semantics.md b/src/ownership/move-semantics.md new file mode 100644 index 00000000..1bb31e19 --- /dev/null +++ b/src/ownership/move-semantics.md @@ -0,0 +1,18 @@ +# Move Semantics + +An assignment will transfer ownership between variables: + +```rust,editable +fn main() { + let s1: String = String::from("Hello!"); + let s2: String = s1; + println!("s2: {s2}"); + // println!("s1: {s1}"); +} +``` + +* The assignment of `s1` to `s2` transfers ownership. +* The data was _moved_ from `s1` and `s1` is no longer accessible. +* When `s1` goes out of scope, nothing happens: it has no ownership. +* When `s2` goes out of scope, the string data is freed. +* There is always _exactly_ one variable binding which owns a value. diff --git a/src/ownership/moved-strings-rust.md b/src/ownership/moved-strings-rust.md new file mode 100644 index 00000000..b3c06592 --- /dev/null +++ b/src/ownership/moved-strings-rust.md @@ -0,0 +1,51 @@ +# Moved Strings in Rust + +```rust,editable +fn main() { + let s1: String = String::from("Rust"); + let s2: String = s1; +} +``` + +* The heap data from `s1` is reused for `s2`. +* When `s1` goes out of scope, nothing happens (it has been moved from). + +Before move to `s2`: + +```bob + Stack Heap +.- - - - - - - - - - - - - -. .- - - - - - - - - - - - - -. +: : : : +: s1 : : : +: +-----------+-------+ : : +----+----+----+----+ : +: | ptr | o---+---+-----+-->| R | u | s | t | : +: | len | 4 | : : +----+----+----+----+ : +: | capacity | 4 | : : : +: +-----------+-------+ : : : +: : `- - - - - - - - - - - - - -' +: : +`- - - - - - - - - - - - - -' +``` + +After move to `s2`: + +```bob + Stack Heap +.- - - - - - - - - - - - - -. .- - - - - - - - - - - - - -. +: : : : +: s1 "(inaccessible)" : : : +: +-----------+-------+ : : +----+----+----+----+ : +: | ptr | o---+---+--+--+-->| R | u | s | t | : +: | len | 4 | : | : +----+----+----+----+ : +: | capacity | 4 | : | : : +: +-----------+-------+ : | : : +: : | `- - - - - - - - - - - - - -' +: s2 : | +: +-----------+-------+ : | +: | ptr | o---+---+--' +: | len | 4 | : +: | capacity | 4 | : +: +-----------+-------+ : +: : +`- - - - - - - - - - - - - -' +``` diff --git a/src/ownership/moves-function-calls.md b/src/ownership/moves-function-calls.md new file mode 100644 index 00000000..84316e49 --- /dev/null +++ b/src/ownership/moves-function-calls.md @@ -0,0 +1,16 @@ +# Moves in Function Calls + +When you pass a value to a function, the value is assigned to the function +parameter. This transfers ownership: + +```rust,editable +fn say_hello(name: String) { + println!("Hello {name}") +} + +fn main() { + let name = String::from("Alice"); + say_hello(name); + // say_hello(name); +} +``` diff --git a/src/ownership/shared-unique-borrows.md b/src/ownership/shared-unique-borrows.md new file mode 100644 index 00000000..510b4be6 --- /dev/null +++ b/src/ownership/shared-unique-borrows.md @@ -0,0 +1,21 @@ +# Shared and Unique Borrows + +Rust puts constraints on the ways you can borrow values: + +* You can have one or more `&T` values at any given time, _or_ +* You can have exactly one `&mut T` value. + +```rust,editable,compile_fail +fn main() { + let mut a: i32 = 10; + let b: &i32 = &a; + + { + let c: &mut i32 = &mut a; + *c = 20; + } + + println!("a: {a}"); + println!("b: {b}"); +} +``` diff --git a/src/pattern-matching.md b/src/pattern-matching.md new file mode 100644 index 00000000..3d2d9763 --- /dev/null +++ b/src/pattern-matching.md @@ -0,0 +1,21 @@ +# Pattern Matching + +The `match` keyword let you match a value against one or more _patterns_. The +comparisons are done from top to bottom and the first match wins. + +The patterns can be simple values, similarly to `switch` in C and C++: + +```rust,editable +fn main() { + let input = 'x'; + + match input { + 'q' => println!("Quitting"), + 'a' | 's' | 'w' | 'd' => println!("Moving around"), + '0'..='9' => println!("Number input"), + _ => println!("Something else"), + } +} +``` + +The `_` pattern is a wildcard pattern which matches any value. diff --git a/src/pattern-matching/destructuring-arrays.md b/src/pattern-matching/destructuring-arrays.md new file mode 100644 index 00000000..1d268115 --- /dev/null +++ b/src/pattern-matching/destructuring-arrays.md @@ -0,0 +1,7 @@ +# Destructuring Arrays + +You can destructure arrays, tuples, and slices by matching on their elements: + +```rust,editable +{{#include ../../third_party/rust-by-example/destructuring-arrays.rs}} +``` diff --git a/src/pattern-matching/destructuring-enums.md b/src/pattern-matching/destructuring-enums.md new file mode 100644 index 00000000..33673db0 --- /dev/null +++ b/src/pattern-matching/destructuring-enums.md @@ -0,0 +1,31 @@ +# Destructuring Enums + +Patterns can also be used to bind variables to parts of your values. This is how +you inspect the structure of your types. Let us start with a simple `enum` type: + +```rust,editable +enum Result { + Ok(i32), + Err(String), +} + +fn divide_in_two(n: i32) -> Result { + if n % 2 == 0 { + Result::Ok(n / 2) + } else { + Result::Err(format!("cannot divide {} into two equal parts", n)) + } +} + +fn main() { + let n = 100; + match divide_in_two(n) { + Result::Ok(half) => println!("{n} divided in two is {half}"), + Result::Err(msg) => println!("sorry, an error happened: {msg}"), + } +} +``` + +Here we have used the arms to _destructure_ the `Result` value. In the first +arm, `half` is bound to the value inside the `Ok` variant. In the second arm, +`msg` is bound to the error message. diff --git a/src/pattern-matching/destructuring-structs.md b/src/pattern-matching/destructuring-structs.md new file mode 100644 index 00000000..74efa201 --- /dev/null +++ b/src/pattern-matching/destructuring-structs.md @@ -0,0 +1,7 @@ +# Destructuring Structs + +You can also destructure `structs`: + +```rust,editable +{{#include ../../third_party/rust-by-example/destructuring-structs.rs}} +``` diff --git a/src/pattern-matching/match-guards.md b/src/pattern-matching/match-guards.md new file mode 100644 index 00000000..5c58e83a --- /dev/null +++ b/src/pattern-matching/match-guards.md @@ -0,0 +1,8 @@ +# Match Guards + +When matching, you can add a _guard_ to a pattern. This is an arbitrary Boolean +expression which will be executed if the pattern matches: + +```rust,editable +{{#include ../../third_party/rust-by-example/match-guards.rs}} +``` diff --git a/src/std.md b/src/std.md new file mode 100644 index 00000000..c1ad0854 --- /dev/null +++ b/src/std.md @@ -0,0 +1,21 @@ +# Standard Library + +Rust comes with a standard library which helps establish a set of common types +used by Rust library and programs. This way, two libraries can work together +smoothly because they both use the same `String` type. + +The common vocabulary types include: + +* [`Option` and `Result`](std/option-result.md) types: used for optional values + and [error handling](error-handling.md). + +* [`String`](std/string.md): the default string type used for owned data. + +* [`Vec`](std/vec.md): a standard extensible vector. + +* [`HashMap`](std/hashmap.md): a hash map type with a configurable hashing + algorithm. + +* [`Box`](std/box.md): an owned pointer for heap-allocated data. + +* [`Rc`](std/rc.md): a shared reference-counted pointer for heap-allocated data. diff --git a/src/std/box-niche.md b/src/std/box-niche.md new file mode 100644 index 00000000..478bf8aa --- /dev/null +++ b/src/std/box-niche.md @@ -0,0 +1,31 @@ +# Niche Optimization + +```rust,editable +#[derive(Debug)] +enum List { + Cons(T, Box>), + Nil, +} + +fn main() { + let list: List = List::Cons(1, Box::new(List::Cons(2, Box::new(List::Nil)))); + println!("{list:?}"); +} +``` + +A `Box` cannot be empty, so the pointer is always valid and non-`null`. This +allows the compiler to optimize the memory layout: + +```bob + Stack Heap +.- - - - - - - - - - - - -. .- - - - - - - - - - - - - - - - - - - - - - - -. +: : : : +: list : : : +: +--------+-------+ : : +--------+--------+ +--------+------+ : +: | 0 | 1 | : : .->| 0 | 2 | .->| ////// | //// | : +: | "1/Tag"| o-----+----+-----+-' | "1/Tag"| o-----+-' | "1/Tag"| null | : +: +--------+-------+ : : +--------+--------+ +--------+------+ : +: : : : +: : : : +`- - - - - - - - - - - - -' '- - - - - - - - - - - - - - - - - - - - - - - -' +``` diff --git a/src/std/box-recursive.md b/src/std/box-recursive.md new file mode 100644 index 00000000..75e616b1 --- /dev/null +++ b/src/std/box-recursive.md @@ -0,0 +1,32 @@ +# Box with Recursive Data Structures + +Recursive data types or data types with dynamic sizes need to use a `Box`: + +```rust,editable +#[derive(Debug)] +enum List { + Cons(T, Box>), + Nil, +} + +fn main() { + let list: List = List::Cons(1, Box::new(List::Cons(2, Box::new(List::Nil)))); + println!("{list:?}"); +} +``` + +```bob + Stack Heap +.- - - - - - - - - - - - -. .- - - - - - - - - - - - - - - - - - - - - - - -. +: : : : +: list : : : +: +--------+-------+ : : +--------+--------+ +--------+------+ : +: | Tag | Cons | : : .->| Tag | Cons | .->| Tag | Nil | : +: | 0 | 1 | : : | | 0 | 2 | | | ////// | //// | : +: | 1 | o-----+----+-----+-' | 1 | o------+-' | ////// | //// | : +: +--------+-------+ : : +--------+--------+ +--------+------+ : +: : : : +: : : : +`- - - - - - - - - - - - -' '- - - - - - - - - - - - - - - - - - - - - - - -' +``` + diff --git a/src/std/box.md b/src/std/box.md new file mode 100644 index 00000000..b46a2fef --- /dev/null +++ b/src/std/box.md @@ -0,0 +1,30 @@ +# `Box` + +[`Box`][1] is an owned pointer to data on the heap: + +```rust,editable +fn main() { + let five = Box::new(5); + println!("five: {}", *five); +} +``` + + +```bob + Stack Heap +.- - - - - - -. .- - - - - - -. +: : : : +: five : : : +: +-----+ : : +-----+ : +: | o---|---+-----+-->| 5 | : +: +-----+ : : +-----+ : +: : : : +: : : : +`- - - - - - -' `- - - - - - -' +``` + +`Box` implements `Deref`, which means that you can [call methods +from `T` directly on a `Box`][2]. + +[1]: https://doc.rust-lang.org/std/boxed/struct.Box.html +[2]: https://doc.rust-lang.org/std/ops/trait.Deref.html#more-on-deref-coercion diff --git a/src/std/hashmap.md b/src/std/hashmap.md new file mode 100644 index 00000000..2ff029b0 --- /dev/null +++ b/src/std/hashmap.md @@ -0,0 +1,26 @@ +# `HashMap` + +Standard hash map with protection against HashDoS attacks: + +```rust,editable +use std::collections::HashMap; + +fn main() { + let mut page_counts = HashMap::new(); + page_counts.insert("Adventures of Huckleberry Finn".to_string(), 207); + page_counts.insert("Grimms' Fairy Tales".to_string(), 751); + page_counts.insert("Pride and Prejudice".to_string(), 303); + + if !page_counts.contains_key("Les Misérables") { + println!("We've know about {} books, but not Les Misérables.", + page_counts.len()); + } + + for book in ["Pride and Prejudice", "Alice's Adventure in Wonderland"] { + match page_counts.get(book) { + Some(count) => println!("{book}: {count} pages"), + None => println!("{book} is unknown.") + } + } +} +``` diff --git a/src/std/option-result.md b/src/std/option-result.md new file mode 100644 index 00000000..ce918140 --- /dev/null +++ b/src/std/option-result.md @@ -0,0 +1,14 @@ +# `Option` and `Result` + +The types represent optional data: + +```rust,editable +fn main() { + let numbers = vec![10, 20, 30]; + let first: Option<&i8> = numbers.first(); + println!("first: {first:?}"); + + let idx: Result = numbers.binary_search(&10); + println!("idx: {idx:?}"); +} +``` diff --git a/src/std/rc.md b/src/std/rc.md new file mode 100644 index 00000000..e565080d --- /dev/null +++ b/src/std/rc.md @@ -0,0 +1,22 @@ +# `Rc` + +[`Rc`][1] is a reference-counted shared pointer. Use this when you need to refer +to the same data from multiple places: + +```rust,editable +use std::rc::Rc; + +fn main() { + let mut a = Rc::new(10); + let mut b = a.clone(); + + println!("a: {a}"); + println!("b: {b}"); +} +``` + +If you need to mutate the data inside an `Rc`, you will need to wrap the data in +a type such as [`Cell` or `RefCell`][2]. + +[1]: https://doc.rust-lang.org/std/rc/struct.Rc.html +[2]: https://doc.rust-lang.org/std/cell/index.html diff --git a/src/std/string.md b/src/std/string.md new file mode 100644 index 00000000..de934d62 --- /dev/null +++ b/src/std/string.md @@ -0,0 +1,22 @@ +# String + +[`String`][1] is the standard heap-allocated growable UTF-8 string buffer: + +```rust,editable +fn main() { + let mut s1 = String::new(); + s1.push_str("Hello"); + println!("s1: len = {}, capacity = {}", s1.len(), s1.capacity()); + + let mut s2 = String::with_capacity(s1.len() + 1); + s2.push_str(&s1); + s2.push('!'); + println!("s2: len = {}, capacity = {}", s2.len(), s2.capacity()); +} +``` + +`String` implements [`Deref`][2], which means that you can call all +`str` methods on a `String`. + +[1]: https://doc.rust-lang.org/std/string/struct.String.html +[2]: https://doc.rust-lang.org/std/string/struct.String.html#deref-methods-str diff --git a/src/std/vec.md b/src/std/vec.md new file mode 100644 index 00000000..c63fbc59 --- /dev/null +++ b/src/std/vec.md @@ -0,0 +1,25 @@ +# `Vec` + +[`Vec`][1] is the standard resizeable heap-allocated buffer: + +```rust,editable +fn main() { + let mut numbers = Vec::new(); + numbers.push(42); + + let mut v1 = Vec::new(); + v1.push(42); + println!("v1: len = {}, capacity = {}", v1.len(), v1.capacity()); + + let mut v2 = Vec::with_capacity(v1.len() + 1); + v2.extend(v1.iter()); + v2.push(9999); + println!("v2: len = {}, capacity = {}", v2.len(), v2.capacity()); +} +``` + +`Vec` implements [`Deref`][2], which means that you can call slice +methods on a `Vec`. + +[1]: https://doc.rust-lang.org/std/vec/struct.Vec.html +[2]: https://doc.rust-lang.org/std/vec/struct.Vec.html#deref-methods-[T] diff --git a/src/structs.md b/src/structs.md new file mode 100644 index 00000000..bbbfd4f5 --- /dev/null +++ b/src/structs.md @@ -0,0 +1,19 @@ +# Structs + +Like C and C++, Rust has support for custom structs: + +```rust,editable +struct Person { + name: String, + age: u8, +} + +fn main() { + let peter = Person { + name: String::from("Peter"), + age: 27, + }; + + println!("{} is {} years old", peter.name, peter.age); +} +``` diff --git a/src/structs/field-shorthand.md b/src/structs/field-shorthand.md new file mode 100644 index 00000000..021592db --- /dev/null +++ b/src/structs/field-shorthand.md @@ -0,0 +1,23 @@ +# Field Shorthand Syntax + +If you already have variables with the right names, then you can create the +struct using a shorthand: + +```rust,editable +#[derive(Debug)] +struct Person { + name: String, + age: u8, +} + +impl Person { + fn new(name: String, age: u8) -> Person { + Person { name, age } + } +} + +fn main() { + let peter = Person::new(String::from("Peter"), 27); + println!("{peter:?}"); +} +``` diff --git a/src/structs/tuple-structs.md b/src/structs/tuple-structs.md new file mode 100644 index 00000000..0e8ea6af --- /dev/null +++ b/src/structs/tuple-structs.md @@ -0,0 +1,33 @@ +# Tuple Structs + +If the field names are unimportant, you can use a tuple struct: + +```rust,editable +struct Point(i32, i32); + +fn main() { + let p = Point(17, 23); + println!("({}, {})", p.0, p.1); +} +``` + +This is often used for single-field wrappers (called newtypes): + +```rust,editable,compile_fail +struct PoundOfForce(f64); +struct Newtons(f64); + +fn compute_thruster_force() -> PoundOfForce { + todo!("Ask a rocket scientist at NASA") +} + +fn set_thruster_force(force: Newtons) { + // ... +} + +fn main() { + let force = compute_thruster_force(); + set_thruster_force(force); +} + +``` diff --git a/src/structure.md b/src/structure.md new file mode 100644 index 00000000..a48cb59c --- /dev/null +++ b/src/structure.md @@ -0,0 +1,22 @@ +# Course Structure + +The course is fast paced and we will cover a lot of ground over the next 3--4 +days: + +* Day 1: Basic Rust, ownership and the borrow checker. +* Day 2: Compound data types, pattern matching, the standard library. +* Day 3: Traits and generics, error handling, testing, unsafe Rust. +* Day 4: Concurrency in Rust and interoperatibilty with other languages + +> **Exercise for Day 4:** Do you interface with some C/C++ code in your project +> which we could attempt to move to Rust? The fewer dependencies the better. +> Parsing code would be ideal. + +## Format + +The course is interactive and your questions will drive our exploration of Rust! + +* Please ask quesitons when you get then, don't save them to the end. +* Discussions are very much encouraged! +* We will likely talk about things ahead of the slides. + * The slides are just a support and we are free to skip them as we like. diff --git a/src/testing.md b/src/testing.md new file mode 100644 index 00000000..16f67935 --- /dev/null +++ b/src/testing.md @@ -0,0 +1,7 @@ +# Testing + +Rust and Cargo come with a simple unit test framework: + +* Unit tests are supported throughout your code. + +* Integration tests are supported via the `tests/` directory. diff --git a/src/testing/doc-tests.md b/src/testing/doc-tests.md new file mode 100644 index 00000000..f46005c4 --- /dev/null +++ b/src/testing/doc-tests.md @@ -0,0 +1,20 @@ +# Documentation Tests + +Rust has built-in support for documentation tests: + +```rust +/// Shorten string will trip the string to the given length. +/// +/// ``` +/// use playground::shorten_string; +/// assert_eq!(shorten_string("Hello World", 5), "Hello"); +/// assert_eq!(shorten_string("Hello World", 20), "Hello World"); +/// ``` +pub fn shorten_string(s: &str, length: usize) -> &str { + &s[..std::cmp::min(length, s.len())] +} +``` + +* Code blocks in `///` comments are automatically seen as Rust code. +* The code will be compiled and executed as part of `cargo test`. +* Test the above code on the [Rust Playground](https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=c8ce535a3778218fed50c2b4c317d15d). diff --git a/src/testing/integration-tests.md b/src/testing/integration-tests.md new file mode 100644 index 00000000..1a45ef17 --- /dev/null +++ b/src/testing/integration-tests.md @@ -0,0 +1,16 @@ +# Integration Tests + +If you want to test your library as a client, use an integration test. + +Create a `.rs` file under `tests/`: + +```rust,ignore +use my_library::init; + +#[test] +fn test_init() { + assert!(init().is_ok()); +} +``` + +These tests only have access to the public API of your crate. diff --git a/src/testing/test-modules.md b/src/testing/test-modules.md new file mode 100644 index 00000000..a89bd098 --- /dev/null +++ b/src/testing/test-modules.md @@ -0,0 +1,27 @@ +# Test Modules + +Unit tests are often put in a nested module (run tests on the +[Playground](https://play.rust-lang.org/)): + +```rust,editable +fn helper(a: &str, b: &str) -> String { + format!("{a} {b}") +} + +pub fn main() { + println!("{}", helper("Hello", "World")); +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_helper() { + assert_eq!(helper("foo", "bar"), "foo bar"); + } +} +``` + +* This lets you unit test private helpers. +* The `#[cfg(test)]` attribute is only active when you run `cargo test`. diff --git a/src/testing/unit-tests.md b/src/testing/unit-tests.md new file mode 100644 index 00000000..3345b237 --- /dev/null +++ b/src/testing/unit-tests.md @@ -0,0 +1,29 @@ +# Unit Tests + +Mark unit tests with `#[test]`: + +```rust,editable +fn first_word(text: &str) -> &str { + match text.find(' ') { + Some(idx) => &text[..idx], + None => &text, + } +} + +#[test] +fn test_empty() { + assert_eq!(first_word(""), ""); +} + +#[test] +fn test_single_word() { + assert_eq!(first_word("Hello"), Some("Hello")); +} + +#[test] +fn test_multiple_words() { + assert_eq!(first_word("Hello World"), Some("Hello")); +} +``` + +Use `cargo test` to find and run the unit tests. diff --git a/src/thanks.md b/src/thanks.md new file mode 100644 index 00000000..c7c21031 --- /dev/null +++ b/src/thanks.md @@ -0,0 +1,10 @@ +# Thanks! + +_Thank you for taking Comprehensive Rust 🦀!_ We hope you enjoyed it and that it +was useful. + +We've had a lot of fun putting the course together. The course is not perfect, +so if you spotted any mistakes or have ideas for improvements, please get in +[contact with us on +GitHub](https://github.com/google/comprehensive-rust/discussions). We would love +to hear from you. diff --git a/src/traits.md b/src/traits.md new file mode 100644 index 00000000..f19ad8ed --- /dev/null +++ b/src/traits.md @@ -0,0 +1,37 @@ +# Traits + +Rust lets you abstract over types with traits. They're similar to interfaces: + +```rust,editable +trait Greet { + fn say_hello(&self); +} + +struct Dog { + name: String, +} + +struct Cat; // No name, cats won't respond to it anyway. + +impl Greet for Dog { + fn say_hello(&self) { + println!("Wuf, my name is {}!", self.name); + } +} + +impl Greet for Cat { + fn say_hello(&self) { + println!("Miau!"); + } +} + +fn main() { + let pets: Vec> = vec![ + Box::new(Dog { name: String::from("Fido") }), + Box::new(Cat), + ]; + for pet in pets { + pet.say_hello(); + } +} +``` diff --git a/src/traits/default-methods.md b/src/traits/default-methods.md new file mode 100644 index 00000000..66bd92eb --- /dev/null +++ b/src/traits/default-methods.md @@ -0,0 +1,28 @@ +# Default Methods + +Traits can implement behavior in terms of other trait methods: + +```rust,editable +trait Equals { + fn equal(&self, other: &Self) -> bool; + fn not_equal(&self, other: &Self) -> bool { + !self.equal(other) + } +} + +#[derive(Debug)] +struct Centimeter(i16); + +impl Equals for Centimeter { + fn equal(&self, other: &Centimeter) -> bool { + self.0 == other.0 + } +} + +fn main() { + let a = Centimeter(10); + let b = Centimeter(20); + println!("{a:?} equals {b:?}: {}", a.equal(&b)); + println!("{a:?} not_equals {b:?}: {}", a.not_equal(&b)); +} +``` diff --git a/src/traits/deriving-traits.md b/src/traits/deriving-traits.md new file mode 100644 index 00000000..d289f9ef --- /dev/null +++ b/src/traits/deriving-traits.md @@ -0,0 +1,19 @@ +# Deriving Traits + +You can let the compiler derive a number of traits: + +```rust,editable +#[derive(Debug, Clone, PartialEq, Eq, Default)] +struct Player { + name: String, + strength: u8, + hit_points: u8, +} + +fn main() { + let p1 = Player::default(); + let p2 = p1.clone(); + println!("Is {:?}\nequal to {:?}?\nThe answer is {}!", &p1, &p2, + if p1 == p2 { "yes" } else { "no" }); +} +``` diff --git a/src/traits/drop.md b/src/traits/drop.md new file mode 100644 index 00000000..aa9cc2b0 --- /dev/null +++ b/src/traits/drop.md @@ -0,0 +1,30 @@ +# The `Drop` Trait + +Values which implement `Drop` can specify code to run when they go out of scope: + +```rust,editable +struct Droppable { + name: &'static str, +} + +impl Drop for Droppable { + fn drop(&mut self) { + println!("Dropping {}", self.name); + } +} + +fn main() { + let a = Droppable { name: "a" }; + { + let b = Droppable { name: "b" }; + { + let c = Droppable { name: "c" }; + let d = Droppable { name: "d" }; + println!("Exiting block B"); + } + println!("Exiting block A"); + } + drop(a); + println!("Exiting main"); +} +``` diff --git a/src/traits/from-into.md b/src/traits/from-into.md new file mode 100644 index 00000000..9b1182ac --- /dev/null +++ b/src/traits/from-into.md @@ -0,0 +1,25 @@ +# `From` and `Into` + +Types implement `From` and `Into` to facilitate type conversions: + +```rust,editable +fn main() { + let s = String::from("hello"); + let addr = std::net::Ipv4Addr::from([127, 0, 0, 1]); + let one = i16::from(true); + let bigger = i32::from(123i16); + println!("{s}, {addr}, {one}, {bigger}"); +} +``` + +`Into` is automatically implemented when `From` is implemented: + +```rust,editable +fn main() { + let s: String = "hello".into(); + let addr: std::net::Ipv4Addr = [127, 0, 0, 1].into(); + let one: i16 = true.into(); + let bigger: i32 = 123i16.into(); + println!("{s}, {addr}, {one}, {bigger}"); +} +``` diff --git a/src/traits/important-traits.md b/src/traits/important-traits.md new file mode 100644 index 00000000..068756d9 --- /dev/null +++ b/src/traits/important-traits.md @@ -0,0 +1,9 @@ +# Important Traits + +We will now look at some of the most common traits of the Rust standard library: + +* `Iterator` and `IntoIterator` used in `for` loops, +* `From` and `Into` used to convert values, +* `Read` and `Write` used for IO, +* `Add`, `Mul`, ... used for operator overloading, and +* `Drop` used for defining destructors. diff --git a/src/traits/iterator.md b/src/traits/iterator.md new file mode 100644 index 00000000..ae0cc90b --- /dev/null +++ b/src/traits/iterator.md @@ -0,0 +1,28 @@ +# Iterators + +You can implement the `Iterator` trait on your own types: + +```rust,editable +struct Fibonacci { + curr: u32, + next: u32, +} + +impl Iterator for Fibonacci { + type Item = u32; + + fn next(&mut self) -> Option { + let new_next = self.curr + self.next; + self.curr = self.next; + self.next = new_next; + Some(self.curr) + } +} + +fn main() { + let fib = Fibonacci { curr: 0, next: 1 }; + for (i, n) in fib.enumerate().take(5) { + println!("fib({i}): {n}"); + } +} +``` diff --git a/src/traits/operators.md b/src/traits/operators.md new file mode 100644 index 00000000..b28bd9f8 --- /dev/null +++ b/src/traits/operators.md @@ -0,0 +1,22 @@ +# `Add`, `Mul`, ... + +Operator overloading is implemented via traits in `std::ops`: + +```rust,editable +#[derive(Debug, Copy, Clone)] +struct Point { x: i32, y: i32 } + +impl std::ops::Add for Point { + type Output = Self; + + fn add(self, other: Self) -> Self { + Self {x: self.x + other.x, y: self.y + other.y} + } +} + +fn main() { + let p1 = Point { x: 10, y: 20 }; + let p2 = Point { x: 100, y: 200 }; + println!("{:?} + {:?} = {:?}", p1, p2, p1 + p2); +} +``` diff --git a/src/traits/read-write.md b/src/traits/read-write.md new file mode 100644 index 00000000..59303408 --- /dev/null +++ b/src/traits/read-write.md @@ -0,0 +1,40 @@ +# `Read` and `Write` + +Using `Read` and `BufRead`, you can abstract over `u8` sources: + +```rust,editable +use std::io::{BufRead, BufReader, Read, Result}; + +fn count_lines(reader: R) -> usize { + let buf_reader = BufReader::new(reader); + buf_reader.lines().count() +} + +fn main() -> Result<()> { + let slice: &[u8] = b"foo\nbar\nbaz\n"; + println!("lines in slice: {}", count_lines(slice)); + + let file = std::fs::File::open(std::env::current_exe()?)?; + println!("lines in file: {}", count_lines(file)); + Ok(()) +} +``` + +Similarly, `Write` lets you abstract over `u8` sinks: + +```rust,editable +use std::io::{Result, Write}; + +fn log(writer: &mut W, msg: &str) -> Result<()> { + writer.write_all(msg.as_bytes())?; + writer.write_all("\n".as_bytes()) +} + +fn main() -> Result<()> { + let mut buffer = Vec::with_capacity(1024); + log(&mut buffer, "Hello")?; + log(&mut buffer, "World")?; + println!("Logged: {:?}", buffer); + Ok(()) +} +``` diff --git a/src/unsafe.md b/src/unsafe.md new file mode 100644 index 00000000..0ce06369 --- /dev/null +++ b/src/unsafe.md @@ -0,0 +1,18 @@ +# Unsafe Rust + +The Rust language has two parts: + +* **Safe Rust:** memory safe, no undefined behavior possible. +* **Unsafe Rust:** can trigger undefined behavior if preconditions are violated. + +We will be seeing mostly safe Rust in this course, but it's important to know +what unsafe Rust is. + +Unsafe Rust gives you access to five new capabilities: + +* Dereference raw pointers. +* Access or modify mutable static variables. +* Access `union` fields. +* Call `unsafe` functions, including `extern` functions +* Implement `unsafe` traits. + diff --git a/src/unsafe/extern-functions.md b/src/unsafe/extern-functions.md new file mode 100644 index 00000000..e1048daa --- /dev/null +++ b/src/unsafe/extern-functions.md @@ -0,0 +1,17 @@ +# Calling External Code + +Functions from other languages might violate the guarantees of Rust. Calling +them is thus unsafe: + +```rust,editable +extern "C" { + fn abs(input: i32) -> i32; +} + +fn main() { + unsafe { + // Undefined behavior if abs misbehaves. + println!("Absolute value of -3 according to C: {}", abs(-3)); + } +} +``` diff --git a/src/unsafe/mutable-static-variables.md b/src/unsafe/mutable-static-variables.md new file mode 100644 index 00000000..5fa0f7cf --- /dev/null +++ b/src/unsafe/mutable-static-variables.md @@ -0,0 +1,28 @@ +# Mutable Static Variables + +It is safe to read an immutable static variable: + +```rust,editable +static HELLO_WORLD: &str = "Hello, world!"; + +fn main() { + println!("name is: {}", HELLO_WORLD); +} +``` + +However, since data races can occur, it is unsafe to read and write mutable +static variables: + +```rust,editable +static mut COUNTER: u32 = 0; + +fn add_to_counter(inc: u32) { + unsafe { COUNTER += inc; } // Potential data race! +} + +fn main() { + add_to_counter(42); + + unsafe { println!("COUNTER: {}", COUNTER); } // Potential data race! +} +``` diff --git a/src/unsafe/raw-pointers.md b/src/unsafe/raw-pointers.md new file mode 100644 index 00000000..14789ca5 --- /dev/null +++ b/src/unsafe/raw-pointers.md @@ -0,0 +1,18 @@ +# Dereferencing Raw Pointers + +Creating pointers is safe, but dereferencing them requires `unsafe`: + +```rust,editable +fn main() { + let mut num = 5; + + let r1 = &mut num as *mut i32; + let r2 = &num as *const i32; + + unsafe { + println!("r1 is: {}", *r1); + *r1 = 10; // Data race if r1 is being written concurrently! + println!("r2 is: {}", *r2); + } +} +``` diff --git a/src/unsafe/unions.md b/src/unsafe/unions.md new file mode 100644 index 00000000..f0199756 --- /dev/null +++ b/src/unsafe/unions.md @@ -0,0 +1,17 @@ +# Unions + +Unions are like enums, but you need to track the active field yourself: + +```rust,editable +#[repr(C)] +union MyUnion { + i: u8, + b: bool, +} + +fn main() { + let u = MyUnion { i: 42 }; + println!("int: {}", unsafe { u.i }); + println!("bool: {}", unsafe { u.b }); // Undefined behavior! +} +``` diff --git a/src/unsafe/unsafe-functions.md b/src/unsafe/unsafe-functions.md new file mode 100644 index 00000000..dc51452f --- /dev/null +++ b/src/unsafe/unsafe-functions.md @@ -0,0 +1,16 @@ +# Calling Unsafe Functions + +A function or method can be marked `unsafe` if it has extra preconditions you +must uphold: + +```rust,editable +fn main() { + let emojis = "🗻∈🌏"; + unsafe { + // Undefined behavior if indices do not lie on UTF-8 sequence boundaries. + println!("{}", emojis.get_unchecked(0..4)); + println!("{}", emojis.get_unchecked(4..7)); + println!("{}", emojis.get_unchecked(7..11)); + } +} +``` diff --git a/src/welcome-day-1.md b/src/welcome-day-1.md new file mode 100644 index 00000000..a6f4fb0c --- /dev/null +++ b/src/welcome-day-1.md @@ -0,0 +1,12 @@ +# Welcome to Day 1 + +This is the first day of Comprehensive Rust. We will cover a lot of ground +today: + +* Basic Rust syntax: variables, scalar and compound types, enums, structs, + references, functions, and methods. + +* Memory management: stack vs heap, manual memory management, scope-based memory + management, and garbage collection. + +* Ownership: move semantics, copying and cloning, borrowing, and lifetimes. diff --git a/src/welcome-day-1/what-is-rust.md b/src/welcome-day-1/what-is-rust.md new file mode 100644 index 00000000..3d9129a8 --- /dev/null +++ b/src/welcome-day-1/what-is-rust.md @@ -0,0 +1,16 @@ +# What is Rust? + +Rust is new programming language which had it's 1.0 release in 2015: + +* Rust is a statically compiled language in a similar role as C++ + * `rustc` uses LLVM as its backend. +* Rust supports many [platforms and + architectures](https://doc.rust-lang.org/nightly/rustc/platform-support.html): + * x86, ARM, WebAssembly, ... + * Linux, Mac, Windows, ... +* Rust is used for a wide range of devices: + * firmware and boot loaders, + * smart displays, + * mobile phones, + * desktops, + * servers. diff --git a/src/welcome-day-2.md b/src/welcome-day-2.md new file mode 100644 index 00000000..7eed08f2 --- /dev/null +++ b/src/welcome-day-2.md @@ -0,0 +1,15 @@ +# Welcome to Day 2 + +Now that we have seen a fair amount of Rust, we will continue with: + +* Structs, enums, methods. + +* Pattern matching: destructering enums, structs, and arrays. + +* Control flow constructs: `if`, `if let`, `while`, `while let`, `break`, and + `continue`. + +* The Standard Library: `String`, `Option` and `Result`, `Vec`, `HashMap`, `Rc` + and `Arc`. + +* Modules: visibility, paths, and filesystem hierarchy. diff --git a/src/welcome-day-3.md b/src/welcome-day-3.md new file mode 100644 index 00000000..8e2fcf68 --- /dev/null +++ b/src/welcome-day-3.md @@ -0,0 +1,16 @@ +# Welcome to Day 3 + +Today, we will cover some more advanced topics of Rust: + +* Traits: deriving traits, default methods, and important standard library + traits. + +* Generics: generic data types, generic methods, monomorphization, and trait + objects. + +* Error handling: panics, `Result`, and the try operator `?`. + +* Testing: unit tests, documentation tests, and integration tests. + +* Unsafe Rust: raw pointers, static variables, unsafe functions, and extern + functions. diff --git a/src/welcome-day-4.md b/src/welcome-day-4.md new file mode 100644 index 00000000..a087cc81 --- /dev/null +++ b/src/welcome-day-4.md @@ -0,0 +1,13 @@ +# Welcome to Day 4 + +Today we will look at two main topics: + +* Concurrency: threads, channels, shared state, `Send` and `Sync`. + +* Android: building binaries and libraries, using AIDL, logging, and + interoperability with C, C++, and Java. + +> We will attempt to call Rust from one of your own projects today. So try to +> find a little corner of your code base where we can move some lines of code to +> Rust. The fewer dependencies and "exotic" types the better. Something that +> parses some raw bytes would be ideal. diff --git a/src/welcome.md b/src/welcome.md new file mode 100644 index 00000000..58438ddb --- /dev/null +++ b/src/welcome.md @@ -0,0 +1,44 @@ +# Welcome to Comprehensive Rust 🦀 + +This is a four day Rust course developed by the Android team. The course covers +the full spectrum of Rust, from basic syntax to advanced topics like generics +and error handling. It also includes Android-specific content on the last day. + +The goal of the course to teach you Rust. We assume you don't know anything +about Rust and hope to + +* Give you a comprehensive understanding of the Rust syntax and language. +* Enable you to modify existing programs and write new programs in Rust. +* Show you common Rust idioms. + +On Day 4, we will cover Android-specific things such as + +* Building Android components in Rust. +* AIDL servers and clients. +* Interoperability with C, C++, and Java. + +## Non-Goals + +Rust is a large language and we won't be able to cover all it in a few days. +Some non-goals of this course are: + +* Learn how to use async Rust --- we'll only talk a bit about async Rust when + covering traditional concurrency primitives. Please see [Asynchronous + Programming in Rust](https://rust-lang.github.io/async-book/) instead for + details on this topic. +* Learn how to develop macros, please see [Chapter 19.5 in the Rust + Book](https://doc.rust-lang.org/book/ch19-06-macros.html) and [Rust by + Example](https://doc.rust-lang.org/rust-by-example/macros.html) instead. +* Learn the details of how to write unsafe Rust. We will talk about unsafe Rust + on Day 3, but we go into the subtle details. Please see [Chapter 19.1 in the + Rust Book](https://doc.rust-lang.org/book/ch19-01-unsafe-rust.html) and the + [Rustonomicon](https://doc.rust-lang.org/nomicon/) instead. + +## Assumptions + +The course assumes that you already know how to program. Rust is a statically +typed language and we will sometimes make comparisons with C and C++ to better +explain or contrast the Rust approach. + +If you know how to program in a dynamically typed language such as Python or +JavaScript, then you will be able to follow along just fine too. diff --git a/src/why-rust.md b/src/why-rust.md new file mode 100644 index 00000000..ac261b80 --- /dev/null +++ b/src/why-rust.md @@ -0,0 +1,7 @@ +# Why Rust? + +Some unique selling points of Rust: + +* Compile time memory safety +* Lack of undefined runtime behavior +* Modern language features diff --git a/src/why-rust/compile-time.md b/src/why-rust/compile-time.md new file mode 100644 index 00000000..b9aeed9f --- /dev/null +++ b/src/why-rust/compile-time.md @@ -0,0 +1,12 @@ +# Compile Time Guarantees + +Static memory management at compile time: + +* No uninitialized variables. +* No memory leaks. +* No double-frees. +* No use-after-free. +* No `NULL` pointers. +* No forgotten locked mutexes. +* No data races between threads. +* No iterator invalidation. diff --git a/src/why-rust/modern.md b/src/why-rust/modern.md new file mode 100644 index 00000000..05b6df5b --- /dev/null +++ b/src/why-rust/modern.md @@ -0,0 +1,15 @@ +# Modern Features + +Rust is built with all the experience gained in the last 40 years. + +## Language Features + +* Enums and pattern matching. +* Generics. +* No overhead FFI. + +## Tooling + +* Great compiler errors. +* Built-in dependency manager. +* Built-in support for testing. diff --git a/src/why-rust/runtime.md b/src/why-rust/runtime.md new file mode 100644 index 00000000..44d86c06 --- /dev/null +++ b/src/why-rust/runtime.md @@ -0,0 +1,6 @@ +# Runtime Guarantees + +No undefined behavior at runtime: + +* Array access is bounds checked. +* Integer overflow is defined. diff --git a/third_party/cxx/LICENSE-APACHE b/third_party/cxx/LICENSE-APACHE new file mode 100644 index 00000000..16fe87b0 --- /dev/null +++ b/third_party/cxx/LICENSE-APACHE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/third_party/cxx/LICENSE-MIT b/third_party/cxx/LICENSE-MIT new file mode 100644 index 00000000..31aa7938 --- /dev/null +++ b/third_party/cxx/LICENSE-MIT @@ -0,0 +1,23 @@ +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/third_party/cxx/README.md b/third_party/cxx/README.md new file mode 100644 index 00000000..1cdbb8da --- /dev/null +++ b/third_party/cxx/README.md @@ -0,0 +1,15 @@ +# CXX + +This directory contains files copied from CXX. Please see + for the full project. + +## License + +CXX is licensed under either of + +* Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or + ) +* MIT license ([LICENSE-MIT](LICENSE-MIT) or + ) + +at your option. diff --git a/third_party/cxx/overview.svg b/third_party/cxx/overview.svg new file mode 100644 index 00000000..df4fcf49 --- /dev/null +++ b/third_party/cxx/overview.svg @@ -0,0 +1,444 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/third_party/rust-by-example/LICENSE-APACHE b/third_party/rust-by-example/LICENSE-APACHE new file mode 100644 index 00000000..16fe87b0 --- /dev/null +++ b/third_party/rust-by-example/LICENSE-APACHE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/third_party/rust-by-example/LICENSE-MIT b/third_party/rust-by-example/LICENSE-MIT new file mode 100644 index 00000000..ac0fda00 --- /dev/null +++ b/third_party/rust-by-example/LICENSE-MIT @@ -0,0 +1,25 @@ +Copyright (c) 2014 Jorge Aparicio + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/third_party/rust-by-example/README.md b/third_party/rust-by-example/README.md new file mode 100644 index 00000000..b8a798b9 --- /dev/null +++ b/third_party/rust-by-example/README.md @@ -0,0 +1,15 @@ +# Rust By Example + +This directory contains examples copied from Rust by Example. Please see + for the full project. + +## License + +Rust by Example is licensed under either of + +* Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or + ) +* MIT license ([LICENSE-MIT](LICENSE-MIT) or + ) + +at your option. diff --git a/third_party/rust-by-example/destructuring-arrays.rs b/third_party/rust-by-example/destructuring-arrays.rs new file mode 100644 index 00000000..02b50dcc --- /dev/null +++ b/third_party/rust-by-example/destructuring-arrays.rs @@ -0,0 +1,10 @@ +#[rustfmt::skip] +fn main() { + let triple = [0, -2, 3]; + println!("Tell me about {triple:?}"); + match triple { + [0, y, z] => println!("First is 0, y = {y}, and z = {z}"), + [1, ..] => println!("First is 1 and the rest were ignored"), + _ => println!("All elements were ignored"), + } +} diff --git a/third_party/rust-by-example/destructuring-structs.rs b/third_party/rust-by-example/destructuring-structs.rs new file mode 100644 index 00000000..7d4deb20 --- /dev/null +++ b/third_party/rust-by-example/destructuring-structs.rs @@ -0,0 +1,14 @@ +struct Foo { + x: (u32, u32), + y: u32, +} + +#[rustfmt::skip] +fn main() { + let foo = Foo { x: (1, 2), y: 3 }; + match foo { + Foo { x: (1, b), y } => println!("x.0 = 1, b = {b}, y = {y}"), + Foo { y: 2, x: i } => println!("y = 2, i = {i:?}"), + Foo { y, .. } => println!("y = {y}, other fields were ignored"), + } +} diff --git a/third_party/rust-by-example/match-guards.rs b/third_party/rust-by-example/match-guards.rs new file mode 100644 index 00000000..77c007ab --- /dev/null +++ b/third_party/rust-by-example/match-guards.rs @@ -0,0 +1,11 @@ +#[rustfmt::skip] +fn main() { + let pair = (2, -2); + println!("Tell me about {pair:?}"); + match pair { + (x, y) if x == y => println!("These are twins"), + (x, y) if x + y == 0 => println!("Antimatter, kaboom!"), + (x, _) if x % 2 == 1 => println!("The first one is odd"), + _ => println!("No correlation..."), + } +} diff --git a/third_party/rust-by-example/webevent.rs b/third_party/rust-by-example/webevent.rs new file mode 100644 index 00000000..597321f5 --- /dev/null +++ b/third_party/rust-by-example/webevent.rs @@ -0,0 +1,24 @@ +enum WebEvent { + PageLoad, // Variant without payload + KeyPress(char), // Tuple struct variant + Click { x: i64, y: i64 }, // Full struct variant +} + +#[rustfmt::skip] +fn inspect(event: WebEvent) { + match event { + WebEvent::PageLoad => println!("page loaded"), + WebEvent::KeyPress(c) => println!("pressed '{c}'"), + WebEvent::Click { x, y } => println!("clicked at x={x}, y={y}"), + } +} + +fn main() { + let load = WebEvent::PageLoad; + let press = WebEvent::KeyPress('x'); + let click = WebEvent::Click { x: 20, y: 80 }; + + inspect(load); + inspect(press); + inspect(click); +} diff --git a/third_party/rust-on-exercism/LICENSE b/third_party/rust-on-exercism/LICENSE new file mode 100644 index 00000000..90e73be0 --- /dev/null +++ b/third_party/rust-on-exercism/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021 Exercism + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/third_party/rust-on-exercism/README.md b/third_party/rust-on-exercism/README.md new file mode 100644 index 00000000..e813bb89 --- /dev/null +++ b/third_party/rust-on-exercism/README.md @@ -0,0 +1,8 @@ +# Exercism Rust Track + +This directory contains exercises copied from the Exercism Rust Track. Please +see for the full project. + +## License + +The Exercism Rust Track is licensed under the MIT license ([LICENSE](LICENSE)). diff --git a/third_party/rust-on-exercism/health-statistics.md b/third_party/rust-on-exercism/health-statistics.md new file mode 100644 index 00000000..a00e4a9b --- /dev/null +++ b/third_party/rust-on-exercism/health-statistics.md @@ -0,0 +1,6 @@ +You're working on implementing a health-monitoring system. As part of that, you +need to keep track of users' health statistics. + +You'll start with some stubbed functions in an `impl` block as well as a `User` +struct definition. Your goal is to implement the stubbed out methods on the +`User` `struct` defined in the `impl` block. diff --git a/third_party/rust-on-exercism/health-statistics.rs b/third_party/rust-on-exercism/health-statistics.rs new file mode 100644 index 00000000..ddd5e905 --- /dev/null +++ b/third_party/rust-on-exercism/health-statistics.rs @@ -0,0 +1,31 @@ +struct User { + name: String, + age: u32, + weight: f32, +} + +impl User { + pub fn new(name: String, age: u32, weight: f32) -> Self { + unimplemented!() + } + + pub fn name(&self) -> &str { + unimplemented!() + } + + pub fn age(&self) -> u32 { + unimplemented!() + } + + pub fn weight(&self) -> f32 { + unimplemented!() + } + + pub fn set_age(&mut self, new_age: u32) { + unimplemented!() + } + + pub fn set_weight(&mut self, new_weight: f32) { + unimplemented!() + } +}