From c212a473ba4d060b209b654c0dd403328d6e137f Mon Sep 17 00:00:00 2001 From: Martin Geisler Date: Wed, 21 Dec 2022 16:36:30 +0100 Subject: [PATCH] =?UTF-8?q?Publish=20Comprehensive=20Rust=20=F0=9F=A6=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 2 + CONTRIBUTING.md | 28 ++ Cargo.toml | 49 ++ LICENSE.txt | 202 ++++++++ README.md | 40 ++ book.toml | 23 + ga4.js | 87 ++++ rustfmt.toml | 1 + src/SUMMARY.md | 237 ++++++++++ src/android.md | 6 + src/android/aidl.md | 7 + src/android/aidl/birthday_service/Android.bp | 38 ++ .../aidl/birthday_service/aidl/Android.bp | 10 + .../birthdayservice/IBirthdayService.aidl | 22 + .../aidl/birthday_service/src/client.rs | 42 ++ src/android/aidl/birthday_service/src/lib.rs | 31 ++ .../aidl/birthday_service/src/server.rs | 33 ++ src/android/aidl/changing.md | 14 + src/android/aidl/client.md | 24 + src/android/aidl/deploy.md | 29 ++ src/android/aidl/implementation.md | 15 + src/android/aidl/interface.md | 18 + src/android/aidl/server.md | 15 + src/android/bpfmt.sh | 27 ++ src/android/build-rules.md | 16 + src/android/build-rules/binary.md | 23 + src/android/build-rules/binary/Android.bp | 5 + src/android/build-rules/binary/src/main.rs | 21 + src/android/build-rules/library.md | 37 ++ src/android/build-rules/library/Android.bp | 16 + src/android/build-rules/library/src/lib.rs | 21 + src/android/build-rules/library/src/main.rs | 24 + src/android/build_all.sh | 130 +++++ src/android/interoperability.md | 10 + src/android/interoperability/cpp.md | 13 + src/android/interoperability/cpp/overview.svg | 1 + src/android/interoperability/java.md | 39 ++ src/android/interoperability/java/Android.bp | 17 + .../interoperability/java/HelloWorld.java | 29 ++ src/android/interoperability/java/src/lib.rs | 33 ++ src/android/interoperability/with-c.md | 26 + .../interoperability/with-c/bindgen.md | 75 +++ .../with-c/bindgen/Android.bp | 36 ++ .../with-c/bindgen/c-library.md | 20 + .../with-c/bindgen/libbirthday.c | 26 + .../with-c/bindgen/libbirthday.h | 23 + .../with-c/bindgen/libbirthday_wrapper.h | 18 + .../interoperability/with-c/bindgen/main.rs | 29 ++ .../interoperability/with-c/calling-rust.md | 1 + .../interoperability/with-c/hand-written.md | 21 + src/android/interoperability/with-c/rust.md | 42 ++ .../with-c/rust/analyze/Android.bp | 5 + .../with-c/rust/analyze/main.c | 24 + .../with-c/rust/libanalyze/Android.bp | 6 + .../with-c/rust/libanalyze/analyze.h | 25 + .../with-c/rust/libanalyze/analyze.rs | 29 ++ src/android/logging.md | 31 ++ src/android/logging/Android.bp | 11 + src/android/logging/src/main.rs | 29 ++ src/android/setup.md | 13 + src/basic-syntax.md | 9 + src/basic-syntax/compound-types.md | 26 + src/basic-syntax/functions-interlude.md | 23 + src/basic-syntax/functions.md | 31 ++ src/basic-syntax/methods.md | 30 ++ src/basic-syntax/references-dangling.md | 19 + src/basic-syntax/references.md | 18 + src/basic-syntax/scalar-types.md | 18 + src/basic-syntax/scopes-shadowing.md | 21 + src/basic-syntax/slices.md | 16 + src/basic-syntax/static-and-const.md | 39 ++ src/basic-syntax/string-slices.md | 20 + src/basic-syntax/type-inference.md | 22 + src/basic-syntax/variables.md | 13 + src/cargo.md | 18 + src/cargo/code-samples.md | 20 + src/cargo/running-locally.md | 66 +++ src/cargo/rust-ecosystem.md | 17 + src/concurrency.md | 8 + src/concurrency/channels.md | 23 + src/concurrency/channels/bounded.md | 27 ++ src/concurrency/channels/unbounded.md | 27 ++ src/concurrency/scoped-threads.md | 33 ++ src/concurrency/send-sync.md | 11 + src/concurrency/send-sync/examples.md | 41 ++ src/concurrency/send-sync/send.md | 9 + src/concurrency/send-sync/sync.md | 10 + src/concurrency/shared_state.md | 11 + src/concurrency/shared_state/arc.md | 25 + src/concurrency/shared_state/example.md | 19 + src/concurrency/shared_state/mutex.md | 28 ++ src/concurrency/threads.md | 26 + src/control-flow.md | 6 + src/control-flow/blocks.md | 36 ++ src/control-flow/break-continue.md | 25 + src/control-flow/for-expressions.md | 16 + src/control-flow/if-expressions.md | 27 ++ src/control-flow/if-let-expressions.md | 17 + src/control-flow/loop-expressions.md | 21 + src/control-flow/match-expressions.md | 23 + src/control-flow/while-expressions.md | 18 + src/control-flow/while-let-expressions.md | 22 + src/credits.md | 28 ++ src/enums.md | 29 ++ src/enums/sizes.md | 25 + src/enums/variant-payloads.md | 8 + src/error-handling.md | 7 + src/error-handling/converting-error-types.md | 51 ++ src/error-handling/deriving-error-enums.md | 35 ++ src/error-handling/error-contexts.md | 39 ++ src/error-handling/panic-unwind.md | 21 + src/error-handling/panics.md | 14 + src/error-handling/result.md | 23 + src/error-handling/try-operator.md | 46 ++ src/exercises/day-1/afternoon.md | 7 + src/exercises/day-1/book-library.md | 42 ++ src/exercises/day-1/book-library.rs | 168 +++++++ src/exercises/day-1/for-loops.md | 75 +++ src/exercises/day-1/for-loops.rs | 69 +++ src/exercises/day-1/implicit-conversions.md | 41 ++ .../day-1/iterators-and-ownership.md | 110 +++++ src/exercises/day-1/morning.md | 7 + src/exercises/day-1/solutions-afternoon.md | 9 + src/exercises/day-1/solutions-morning.md | 9 + src/exercises/day-2/afternoon.md | 3 + src/exercises/day-2/health-statistics.md | 32 ++ src/exercises/day-2/luhn.md | 35 ++ src/exercises/day-2/luhn.rs | 88 ++++ src/exercises/day-2/morning.md | 7 + src/exercises/day-2/points-polygons.md | 41 ++ src/exercises/day-2/points-polygons.rs | 223 +++++++++ src/exercises/day-2/solutions-afternoon.md | 17 + src/exercises/day-2/solutions-morning.md | 12 + src/exercises/day-2/strings-iterators.md | 21 + src/exercises/day-2/strings-iterators.rs | 75 +++ src/exercises/day-3/afternoon.md | 3 + src/exercises/day-3/morning.md | 3 + src/exercises/day-3/safe-ffi-wrapper.md | 47 ++ src/exercises/day-3/safe-ffi-wrapper.rs | 110 +++++ src/exercises/day-3/simple-gui.md | 91 ++++ src/exercises/day-3/simple-gui.rs | 160 +++++++ src/exercises/day-3/solutions-afternoon.md | 9 + src/exercises/day-3/solutions-morning.md | 9 + src/exercises/day-4/afternoon.md | 8 + src/exercises/day-4/dining-philosophers.md | 37 ++ src/exercises/day-4/dining-philosophers.rs | 95 ++++ src/exercises/day-4/link-checker.md | 80 ++++ src/exercises/day-4/link-checker.rs | 89 ++++ src/exercises/day-4/morning.md | 8 + src/exercises/day-4/solutions-morning.md | 10 + src/exercises/solutions.md | 12 + src/generics.md | 4 + src/generics/closures.md | 21 + src/generics/data-types.md | 17 + src/generics/impl-trait.md | 20 + src/generics/methods.md | 21 + src/generics/monomorphization.md | 32 ++ src/generics/trait-bounds.md | 17 + src/generics/trait-objects.md | 86 ++++ src/hello-world.md | 18 + src/hello-world/small-example.md | 20 + src/memory-management.md | 15 + src/memory-management/comparison.md | 34 ++ src/memory-management/garbage-collection.md | 17 + src/memory-management/manual.md | 20 + src/memory-management/rust.md | 9 + src/memory-management/scope-based.md | 30 ++ src/memory-management/stack-vs-heap.md | 12 + src/memory-management/stack.md | 24 + src/methods.md | 27 ++ src/methods/example.md | 41 ++ src/methods/receiver.md | 14 + src/modules.md | 24 + src/modules/filesystem.md | 22 + src/modules/paths.md | 11 + src/modules/visibility.md | 33 ++ src/other-resources.md | 60 +++ src/ownership.md | 20 + src/ownership/borrowing.md | 23 + src/ownership/copy-clone.md | 31 ++ src/ownership/double-free-modern-cpp.md | 51 ++ src/ownership/lifetimes-data-structures.md | 22 + src/ownership/lifetimes-function-calls.md | 25 + src/ownership/lifetimes.md | 12 + src/ownership/move-semantics.md | 18 + src/ownership/moved-strings-rust.md | 51 ++ src/ownership/moves-function-calls.md | 16 + src/ownership/shared-unique-borrows.md | 21 + src/pattern-matching.md | 21 + src/pattern-matching/destructuring-arrays.md | 7 + src/pattern-matching/destructuring-enums.md | 31 ++ src/pattern-matching/destructuring-structs.md | 7 + src/pattern-matching/match-guards.md | 8 + src/std.md | 21 + src/std/box-niche.md | 31 ++ src/std/box-recursive.md | 32 ++ src/std/box.md | 30 ++ src/std/hashmap.md | 26 + src/std/option-result.md | 14 + src/std/rc.md | 22 + src/std/string.md | 22 + src/std/vec.md | 25 + src/structs.md | 19 + src/structs/field-shorthand.md | 23 + src/structs/tuple-structs.md | 33 ++ src/structure.md | 22 + src/testing.md | 7 + src/testing/doc-tests.md | 20 + src/testing/integration-tests.md | 16 + src/testing/test-modules.md | 27 ++ src/testing/unit-tests.md | 29 ++ src/thanks.md | 10 + src/traits.md | 37 ++ src/traits/default-methods.md | 28 ++ src/traits/deriving-traits.md | 19 + src/traits/drop.md | 30 ++ src/traits/from-into.md | 25 + src/traits/important-traits.md | 9 + src/traits/iterator.md | 28 ++ src/traits/operators.md | 22 + src/traits/read-write.md | 40 ++ src/unsafe.md | 18 + src/unsafe/extern-functions.md | 17 + src/unsafe/mutable-static-variables.md | 28 ++ src/unsafe/raw-pointers.md | 18 + src/unsafe/unions.md | 17 + src/unsafe/unsafe-functions.md | 16 + src/welcome-day-1.md | 12 + src/welcome-day-1/what-is-rust.md | 16 + src/welcome-day-2.md | 15 + src/welcome-day-3.md | 16 + src/welcome-day-4.md | 13 + src/welcome.md | 44 ++ src/why-rust.md | 7 + src/why-rust/compile-time.md | 12 + src/why-rust/modern.md | 15 + src/why-rust/runtime.md | 6 + third_party/cxx/LICENSE-APACHE | 201 ++++++++ third_party/cxx/LICENSE-MIT | 23 + third_party/cxx/README.md | 15 + third_party/cxx/overview.svg | 444 ++++++++++++++++++ third_party/rust-by-example/LICENSE-APACHE | 201 ++++++++ third_party/rust-by-example/LICENSE-MIT | 25 + third_party/rust-by-example/README.md | 15 + .../rust-by-example/destructuring-arrays.rs | 10 + .../rust-by-example/destructuring-structs.rs | 14 + third_party/rust-by-example/match-guards.rs | 11 + third_party/rust-by-example/webevent.rs | 24 + third_party/rust-on-exercism/LICENSE | 21 + third_party/rust-on-exercism/README.md | 8 + .../rust-on-exercism/health-statistics.md | 6 + .../rust-on-exercism/health-statistics.rs | 31 ++ 252 files changed, 8047 insertions(+) create mode 100644 .gitignore create mode 100644 CONTRIBUTING.md create mode 100644 Cargo.toml create mode 100644 LICENSE.txt create mode 100644 README.md create mode 100644 book.toml create mode 100644 ga4.js create mode 100644 rustfmt.toml create mode 100644 src/SUMMARY.md create mode 100644 src/android.md create mode 100644 src/android/aidl.md create mode 100644 src/android/aidl/birthday_service/Android.bp create mode 100644 src/android/aidl/birthday_service/aidl/Android.bp create mode 100644 src/android/aidl/birthday_service/aidl/com/example/birthdayservice/IBirthdayService.aidl create mode 100644 src/android/aidl/birthday_service/src/client.rs create mode 100644 src/android/aidl/birthday_service/src/lib.rs create mode 100644 src/android/aidl/birthday_service/src/server.rs create mode 100644 src/android/aidl/changing.md create mode 100644 src/android/aidl/client.md create mode 100644 src/android/aidl/deploy.md create mode 100644 src/android/aidl/implementation.md create mode 100644 src/android/aidl/interface.md create mode 100644 src/android/aidl/server.md create mode 100755 src/android/bpfmt.sh create mode 100644 src/android/build-rules.md create mode 100644 src/android/build-rules/binary.md create mode 100644 src/android/build-rules/binary/Android.bp create mode 100644 src/android/build-rules/binary/src/main.rs create mode 100644 src/android/build-rules/library.md create mode 100644 src/android/build-rules/library/Android.bp create mode 100644 src/android/build-rules/library/src/lib.rs create mode 100644 src/android/build-rules/library/src/main.rs create mode 100755 src/android/build_all.sh create mode 100644 src/android/interoperability.md create mode 100644 src/android/interoperability/cpp.md create mode 120000 src/android/interoperability/cpp/overview.svg create mode 100644 src/android/interoperability/java.md create mode 100644 src/android/interoperability/java/Android.bp create mode 100644 src/android/interoperability/java/HelloWorld.java create mode 100644 src/android/interoperability/java/src/lib.rs create mode 100644 src/android/interoperability/with-c.md create mode 100644 src/android/interoperability/with-c/bindgen.md create mode 100644 src/android/interoperability/with-c/bindgen/Android.bp create mode 100644 src/android/interoperability/with-c/bindgen/c-library.md create mode 100644 src/android/interoperability/with-c/bindgen/libbirthday.c create mode 100644 src/android/interoperability/with-c/bindgen/libbirthday.h create mode 100644 src/android/interoperability/with-c/bindgen/libbirthday_wrapper.h create mode 100644 src/android/interoperability/with-c/bindgen/main.rs create mode 100644 src/android/interoperability/with-c/calling-rust.md create mode 100644 src/android/interoperability/with-c/hand-written.md create mode 100644 src/android/interoperability/with-c/rust.md create mode 100644 src/android/interoperability/with-c/rust/analyze/Android.bp create mode 100644 src/android/interoperability/with-c/rust/analyze/main.c create mode 100644 src/android/interoperability/with-c/rust/libanalyze/Android.bp create mode 100644 src/android/interoperability/with-c/rust/libanalyze/analyze.h create mode 100644 src/android/interoperability/with-c/rust/libanalyze/analyze.rs create mode 100644 src/android/logging.md create mode 100644 src/android/logging/Android.bp create mode 100644 src/android/logging/src/main.rs create mode 100644 src/android/setup.md create mode 100644 src/basic-syntax.md create mode 100644 src/basic-syntax/compound-types.md create mode 100644 src/basic-syntax/functions-interlude.md create mode 100644 src/basic-syntax/functions.md create mode 100644 src/basic-syntax/methods.md create mode 100644 src/basic-syntax/references-dangling.md create mode 100644 src/basic-syntax/references.md create mode 100644 src/basic-syntax/scalar-types.md create mode 100644 src/basic-syntax/scopes-shadowing.md create mode 100644 src/basic-syntax/slices.md create mode 100644 src/basic-syntax/static-and-const.md create mode 100644 src/basic-syntax/string-slices.md create mode 100644 src/basic-syntax/type-inference.md create mode 100644 src/basic-syntax/variables.md create mode 100644 src/cargo.md create mode 100644 src/cargo/code-samples.md create mode 100644 src/cargo/running-locally.md create mode 100644 src/cargo/rust-ecosystem.md create mode 100644 src/concurrency.md create mode 100644 src/concurrency/channels.md create mode 100644 src/concurrency/channels/bounded.md create mode 100644 src/concurrency/channels/unbounded.md create mode 100644 src/concurrency/scoped-threads.md create mode 100644 src/concurrency/send-sync.md create mode 100644 src/concurrency/send-sync/examples.md create mode 100644 src/concurrency/send-sync/send.md create mode 100644 src/concurrency/send-sync/sync.md create mode 100644 src/concurrency/shared_state.md create mode 100644 src/concurrency/shared_state/arc.md create mode 100644 src/concurrency/shared_state/example.md create mode 100644 src/concurrency/shared_state/mutex.md create mode 100644 src/concurrency/threads.md create mode 100644 src/control-flow.md create mode 100644 src/control-flow/blocks.md create mode 100644 src/control-flow/break-continue.md create mode 100644 src/control-flow/for-expressions.md create mode 100644 src/control-flow/if-expressions.md create mode 100644 src/control-flow/if-let-expressions.md create mode 100644 src/control-flow/loop-expressions.md create mode 100644 src/control-flow/match-expressions.md create mode 100644 src/control-flow/while-expressions.md create mode 100644 src/control-flow/while-let-expressions.md create mode 100644 src/credits.md create mode 100644 src/enums.md create mode 100644 src/enums/sizes.md create mode 100644 src/enums/variant-payloads.md create mode 100644 src/error-handling.md create mode 100644 src/error-handling/converting-error-types.md create mode 100644 src/error-handling/deriving-error-enums.md create mode 100644 src/error-handling/error-contexts.md create mode 100644 src/error-handling/panic-unwind.md create mode 100644 src/error-handling/panics.md create mode 100644 src/error-handling/result.md create mode 100644 src/error-handling/try-operator.md create mode 100644 src/exercises/day-1/afternoon.md create mode 100644 src/exercises/day-1/book-library.md create mode 100644 src/exercises/day-1/book-library.rs create mode 100644 src/exercises/day-1/for-loops.md create mode 100644 src/exercises/day-1/for-loops.rs create mode 100644 src/exercises/day-1/implicit-conversions.md create mode 100644 src/exercises/day-1/iterators-and-ownership.md create mode 100644 src/exercises/day-1/morning.md create mode 100644 src/exercises/day-1/solutions-afternoon.md create mode 100644 src/exercises/day-1/solutions-morning.md create mode 100644 src/exercises/day-2/afternoon.md create mode 100644 src/exercises/day-2/health-statistics.md create mode 100644 src/exercises/day-2/luhn.md create mode 100644 src/exercises/day-2/luhn.rs create mode 100644 src/exercises/day-2/morning.md create mode 100644 src/exercises/day-2/points-polygons.md create mode 100644 src/exercises/day-2/points-polygons.rs create mode 100644 src/exercises/day-2/solutions-afternoon.md create mode 100644 src/exercises/day-2/solutions-morning.md create mode 100644 src/exercises/day-2/strings-iterators.md create mode 100644 src/exercises/day-2/strings-iterators.rs create mode 100644 src/exercises/day-3/afternoon.md create mode 100644 src/exercises/day-3/morning.md create mode 100644 src/exercises/day-3/safe-ffi-wrapper.md create mode 100644 src/exercises/day-3/safe-ffi-wrapper.rs create mode 100644 src/exercises/day-3/simple-gui.md create mode 100644 src/exercises/day-3/simple-gui.rs create mode 100644 src/exercises/day-3/solutions-afternoon.md create mode 100644 src/exercises/day-3/solutions-morning.md create mode 100644 src/exercises/day-4/afternoon.md create mode 100644 src/exercises/day-4/dining-philosophers.md create mode 100644 src/exercises/day-4/dining-philosophers.rs create mode 100644 src/exercises/day-4/link-checker.md create mode 100644 src/exercises/day-4/link-checker.rs create mode 100644 src/exercises/day-4/morning.md create mode 100644 src/exercises/day-4/solutions-morning.md create mode 100644 src/exercises/solutions.md create mode 100644 src/generics.md create mode 100644 src/generics/closures.md create mode 100644 src/generics/data-types.md create mode 100644 src/generics/impl-trait.md create mode 100644 src/generics/methods.md create mode 100644 src/generics/monomorphization.md create mode 100644 src/generics/trait-bounds.md create mode 100644 src/generics/trait-objects.md create mode 100644 src/hello-world.md create mode 100644 src/hello-world/small-example.md create mode 100644 src/memory-management.md create mode 100644 src/memory-management/comparison.md create mode 100644 src/memory-management/garbage-collection.md create mode 100644 src/memory-management/manual.md create mode 100644 src/memory-management/rust.md create mode 100644 src/memory-management/scope-based.md create mode 100644 src/memory-management/stack-vs-heap.md create mode 100644 src/memory-management/stack.md create mode 100644 src/methods.md create mode 100644 src/methods/example.md create mode 100644 src/methods/receiver.md create mode 100644 src/modules.md create mode 100644 src/modules/filesystem.md create mode 100644 src/modules/paths.md create mode 100644 src/modules/visibility.md create mode 100644 src/other-resources.md create mode 100644 src/ownership.md create mode 100644 src/ownership/borrowing.md create mode 100644 src/ownership/copy-clone.md create mode 100644 src/ownership/double-free-modern-cpp.md create mode 100644 src/ownership/lifetimes-data-structures.md create mode 100644 src/ownership/lifetimes-function-calls.md create mode 100644 src/ownership/lifetimes.md create mode 100644 src/ownership/move-semantics.md create mode 100644 src/ownership/moved-strings-rust.md create mode 100644 src/ownership/moves-function-calls.md create mode 100644 src/ownership/shared-unique-borrows.md create mode 100644 src/pattern-matching.md create mode 100644 src/pattern-matching/destructuring-arrays.md create mode 100644 src/pattern-matching/destructuring-enums.md create mode 100644 src/pattern-matching/destructuring-structs.md create mode 100644 src/pattern-matching/match-guards.md create mode 100644 src/std.md create mode 100644 src/std/box-niche.md create mode 100644 src/std/box-recursive.md create mode 100644 src/std/box.md create mode 100644 src/std/hashmap.md create mode 100644 src/std/option-result.md create mode 100644 src/std/rc.md create mode 100644 src/std/string.md create mode 100644 src/std/vec.md create mode 100644 src/structs.md create mode 100644 src/structs/field-shorthand.md create mode 100644 src/structs/tuple-structs.md create mode 100644 src/structure.md create mode 100644 src/testing.md create mode 100644 src/testing/doc-tests.md create mode 100644 src/testing/integration-tests.md create mode 100644 src/testing/test-modules.md create mode 100644 src/testing/unit-tests.md create mode 100644 src/thanks.md create mode 100644 src/traits.md create mode 100644 src/traits/default-methods.md create mode 100644 src/traits/deriving-traits.md create mode 100644 src/traits/drop.md create mode 100644 src/traits/from-into.md create mode 100644 src/traits/important-traits.md create mode 100644 src/traits/iterator.md create mode 100644 src/traits/operators.md create mode 100644 src/traits/read-write.md create mode 100644 src/unsafe.md create mode 100644 src/unsafe/extern-functions.md create mode 100644 src/unsafe/mutable-static-variables.md create mode 100644 src/unsafe/raw-pointers.md create mode 100644 src/unsafe/unions.md create mode 100644 src/unsafe/unsafe-functions.md create mode 100644 src/welcome-day-1.md create mode 100644 src/welcome-day-1/what-is-rust.md create mode 100644 src/welcome-day-2.md create mode 100644 src/welcome-day-3.md create mode 100644 src/welcome-day-4.md create mode 100644 src/welcome.md create mode 100644 src/why-rust.md create mode 100644 src/why-rust/compile-time.md create mode 100644 src/why-rust/modern.md create mode 100644 src/why-rust/runtime.md create mode 100644 third_party/cxx/LICENSE-APACHE create mode 100644 third_party/cxx/LICENSE-MIT create mode 100644 third_party/cxx/README.md create mode 100644 third_party/cxx/overview.svg create mode 100644 third_party/rust-by-example/LICENSE-APACHE create mode 100644 third_party/rust-by-example/LICENSE-MIT create mode 100644 third_party/rust-by-example/README.md create mode 100644 third_party/rust-by-example/destructuring-arrays.rs create mode 100644 third_party/rust-by-example/destructuring-structs.rs create mode 100644 third_party/rust-by-example/match-guards.rs create mode 100644 third_party/rust-by-example/webevent.rs create mode 100644 third_party/rust-on-exercism/LICENSE create mode 100644 third_party/rust-on-exercism/README.md create mode 100644 third_party/rust-on-exercism/health-statistics.md create mode 100644 third_party/rust-on-exercism/health-statistics.rs 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!() + } +}