From 6920a4787b768f92d8e7aad19451fb23d0989361 Mon Sep 17 00:00:00 2001 From: Martin Geisler Date: Tue, 30 May 2023 16:34:35 +0200 Subject: [PATCH] da: Sync translation (#714) * da: Sync translation This resulted in some rewrapped strings due to how `msgmerge` wraps the strings compared to how `mdbook-xgettext` extracts them. I also translated a few strings. * Apply suggestions from code review --- po/da.po | 1237 +++++++++++++++++++++++++++--------------------------- 1 file changed, 625 insertions(+), 612 deletions(-) diff --git a/po/da.po b/po/da.po index 65e3f95a..89c8cd18 100644 --- a/po/da.po +++ b/po/da.po @@ -929,7 +929,7 @@ msgid "Async Traits" msgstr "" #: src/SUMMARY.md:295 -msgid "Elevator Operations" +msgid "Broadcast Chat Application" msgstr "" #: src/SUMMARY.md:298 @@ -1000,8 +1000,11 @@ msgstr "# Velkommen til Comprehensive Rust 🦀" msgid "" "[![Build workflow](https://img.shields.io/github/actions/workflow/status/" "google/comprehensive-rust/build.yml?style=flat-square)](https://github.com/" -"google/comprehensive-rust/actions/workflows/build.yml)" +"google/comprehensive-rust/actions/workflows/build.yml?query=branch%3Amain)" msgstr "" +"[![Build workflow](https://img.shields.io/github/actions/workflow/status/" +"google/comprehensive-rust/build.yml?style=flat-square)](https://github.com/" +"google/comprehensive-rust/actions/workflows/build.yml?query=branch%3Amain)" #: src/welcome.md:3 msgid "Build workflow" @@ -1011,7 +1014,7 @@ msgstr "" msgid "" "[![Build workflow](https://img.shields.io/github/actions/workflow/status/" "google/comprehensive-rust/build.yml?style=flat-square)](https://github.com/" -"google/comprehensive-rust/actions/workflows/build.yml)\n" +"google/comprehensive-rust/actions/workflows/build.yml?query=branch%3Amain)\n" "[![GitHub contributors](https://img.shields.io/github/contributors/google/" "comprehensive-rust?style=flat-square)](https://github.com/google/" "comprehensive-rust/graphs/contributors)" @@ -1080,14 +1083,14 @@ msgstr "" msgid "" "The first three days show you the fundamentals of Rust. Following this, " "you're\n" -"invited to dive into one or more spezialized topics:" +"invited to dive into one or more specialized topics:" msgstr "" #: src/welcome.md:21 msgid "" "* [Android](android.md): a half-day course on using Rust for Android " "platform\n" -" development (AOSP). This includes interoperability wtih C, C++, and Java.\n" +" development (AOSP). This includes interoperability with C, C++, and Java.\n" "* [Bare-metal](bare-metal.md): a full day class on using Rust for bare-" "metal\n" " (embedded) development. Both microcontrollers and application processors " @@ -1097,7 +1100,7 @@ msgid "" "We\n" " cover both classical concurrency (preemptively scheduling using threads " "and\n" -" mutextes) and async/await concurrency (cooperative multitasking using\n" +" mutexes) and async/await concurrency (cooperative multitasking using\n" " futures)." msgstr "" @@ -1147,12 +1150,12 @@ msgstr "" #: src/hello-world.md:20 src/hello-world/small-example.md:21 src/why-rust.md:9 #: src/why-rust/compile-time.md:14 src/why-rust/runtime.md:8 #: src/why-rust/modern.md:19 src/basic-syntax/scalar-types.md:19 -#: src/basic-syntax/compound-types.md:28 src/basic-syntax/slices.md:18 -#: src/basic-syntax/string-slices.md:25 src/basic-syntax/functions.md:33 -#: src/basic-syntax/rustdoc.md:22 src/basic-syntax/methods.md:32 -#: src/basic-syntax/functions-interlude.md:25 src/exercises/day-1/morning.md:9 -#: src/exercises/day-1/for-loops.md:90 src/basic-syntax/variables.md:15 -#: src/basic-syntax/type-inference.md:24 +#: src/basic-syntax/compound-types.md:28 src/basic-syntax/references.md:21 +#: src/basic-syntax/slices.md:18 src/basic-syntax/string-slices.md:25 +#: src/basic-syntax/functions.md:33 src/basic-syntax/rustdoc.md:22 +#: src/basic-syntax/methods.md:32 src/basic-syntax/functions-interlude.md:25 +#: src/exercises/day-1/morning.md:9 src/exercises/day-1/for-loops.md:90 +#: src/basic-syntax/variables.md:15 src/basic-syntax/type-inference.md:24 #: src/basic-syntax/static-and-const.md:46 #: src/basic-syntax/scopes-shadowing.md:23 src/memory-management/stack.md:26 #: src/memory-management/rust.md:12 src/ownership/move-semantics.md:20 @@ -1161,9 +1164,10 @@ msgstr "" #: src/ownership/lifetimes-function-calls.md:27 #: src/ownership/lifetimes-data-structures.md:23 #: src/exercises/day-1/afternoon.md:9 src/exercises/day-1/book-library.md:100 -#: src/structs/tuple-structs.md:35 src/structs/field-shorthand.md:25 -#: src/enums.md:31 src/enums/variant-payloads.md:33 src/enums/sizes.md:27 -#: src/methods.md:28 src/methods/receiver.md:23 src/methods/example.md:44 +#: src/structs.md:29 src/structs/tuple-structs.md:35 +#: src/structs/field-shorthand.md:25 src/enums.md:31 +#: src/enums/variant-payloads.md:33 src/enums/sizes.md:27 src/methods.md:28 +#: src/methods/receiver.md:22 src/methods/example.md:44 #: src/pattern-matching.md:23 src/pattern-matching/destructuring-enums.md:33 #: src/pattern-matching/destructuring-structs.md:21 #: src/pattern-matching/destructuring-arrays.md:19 @@ -1252,12 +1256,12 @@ msgstr "" #: src/hello-world.md:40 src/hello-world/small-example.md:44 src/why-rust.md:24 #: src/why-rust/compile-time.md:35 src/why-rust/runtime.md:22 #: src/why-rust/modern.md:66 src/basic-syntax/scalar-types.md:43 -#: src/basic-syntax/compound-types.md:62 src/basic-syntax/references.md:28 +#: src/basic-syntax/compound-types.md:62 src/basic-syntax/references.md:29 #: src/basic-syntax/slices.md:36 src/basic-syntax/string-slices.md:44 #: src/basic-syntax/functions.md:41 src/basic-syntax/rustdoc.md:33 -#: src/basic-syntax/methods.md:45 src/exercises/day-1/morning.md:28 -#: src/exercises/day-1/for-loops.md:95 src/basic-syntax/variables.md:20 -#: src/basic-syntax/type-inference.md:48 +#: src/basic-syntax/methods.md:45 src/basic-syntax/functions-interlude.md:30 +#: src/exercises/day-1/morning.md:28 src/exercises/day-1/for-loops.md:95 +#: src/basic-syntax/variables.md:20 src/basic-syntax/type-inference.md:48 #: src/basic-syntax/static-and-const.md:52 #: src/basic-syntax/scopes-shadowing.md:39 src/memory-management/stack.md:49 #: src/memory-management/rust.md:18 src/ownership/move-semantics.md:26 @@ -1266,10 +1270,10 @@ msgstr "" #: src/ownership/lifetimes-function-calls.md:60 #: src/ownership/lifetimes-data-structures.md:30 #: src/exercises/day-1/afternoon.md:15 src/exercises/day-1/book-library.md:104 -#: src/structs.md:41 src/structs/tuple-structs.md:43 +#: src/structs.md:42 src/structs/tuple-structs.md:43 #: src/structs/field-shorthand.md:72 src/enums.md:41 #: src/enums/variant-payloads.md:45 src/enums/sizes.md:155 src/methods.md:41 -#: src/methods/receiver.md:29 src/methods/example.md:53 +#: src/methods/receiver.md:28 src/methods/example.md:53 #: src/pattern-matching.md:35 src/pattern-matching/destructuring-enums.md:39 #: src/pattern-matching/destructuring-structs.md:29 #: src/pattern-matching/destructuring-arrays.md:46 @@ -1286,7 +1290,7 @@ msgstr "" #: src/std/rc.md:69 src/modules.md:32 src/modules/visibility.md:48 #: src/modules/filesystem.md:71 src/exercises/day-2/afternoon.md:11 #: src/generics/data-types.md:25 src/generics/methods.md:31 -#: src/traits/trait-objects.md:83 src/traits/default-methods.md:41 +#: src/traits/trait-objects.md:83 src/traits/default-methods.md:60 #: src/traits/trait-bounds.md:50 src/traits/impl-trait.md:44 #: src/traits/iterator.md:42 src/traits/from-iterator.md:26 #: src/traits/from-into.md:33 src/traits/drop.md:42 src/traits/default.md:47 @@ -1351,7 +1355,7 @@ msgstr "" #: src/running-the-course.md:3 src/running-the-course/course-structure.md:3 msgid "> This page is for the course instructor." -msgstr "" +msgstr "> Denne side er for kursuslederen." #: src/running-the-course.md:5 msgid "" @@ -1433,7 +1437,7 @@ msgstr "" #: src/running-the-course/course-structure.md:1 msgid "# Course Structure" -msgstr "" +msgstr "# Kursets struktur" #: src/running-the-course/course-structure.md:5 msgid "The course is fast paced and covers a lot of ground:" @@ -1458,13 +1462,13 @@ msgstr "" #: src/running-the-course/course-structure.md:16 msgid "### Android" -msgstr "" +msgstr "### Android" #: src/running-the-course/course-structure.md:18 msgid "" "The [Android Deep Dive](../android.md) is a half-day course on using Rust " "for\n" -"Android platform development. This includes interoperability wtih C, C++, " +"Android platform development. This includes interoperability with C, C++, " "and\n" "Java." msgstr "" @@ -1551,7 +1555,7 @@ msgstr "" #: src/running-the-course/keyboard-shortcuts.md:1 msgid "# Keyboard Shortcuts" -msgstr "" +msgstr "# Tastaturgenveje" #: src/running-the-course/keyboard-shortcuts.md:3 msgid "There are several useful keyboard shortcuts in mdBook:" @@ -1567,7 +1571,7 @@ msgstr "" #: src/running-the-course/translations.md:1 msgid "# Translations" -msgstr "" +msgstr "# Oversættelser" #: src/running-the-course/translations.md:3 msgid "" @@ -1595,7 +1599,7 @@ msgstr "" #: src/cargo.md:1 msgid "# Using Cargo" -msgstr "" +msgstr "# Brug af Cargo" #: src/cargo.md:3 msgid "" @@ -1610,11 +1614,11 @@ msgstr "" #: src/cargo.md:8 msgid "## Installation" -msgstr "" +msgstr "## Installation" #: src/cargo.md:10 msgid "### Rustup (Recommended)" -msgstr "" +msgstr "### Rustup (anbefalet)" #: src/cargo.md:12 msgid "" @@ -1636,7 +1640,7 @@ msgstr "" #: src/cargo.md:18 msgid "#### Debian" -msgstr "" +msgstr "#### Debian" #: src/cargo.md:20 msgid "" @@ -1650,6 +1654,9 @@ msgid "" "$ sudo apt install cargo rust-src rustfmt\n" "```" msgstr "" +"```shell\n" +"$ sudo apt install cargo rust-src rustfmt\n" +"```" #: src/cargo.md:26 msgid "" @@ -1699,12 +1706,13 @@ msgstr "" #: src/cargo/rust-ecosystem.md:21 src/hello-world.md:25 #: src/hello-world/small-example.md:27 src/why-rust/runtime.md:10 #: src/why-rust/modern.md:21 src/basic-syntax/compound-types.md:30 +#: src/basic-syntax/references.md:23 #: src/pattern-matching/destructuring-enums.md:35 #: src/error-handling/try-operator.md:48 #: src/error-handling/converting-error-types-example.md:50 #: src/concurrency/threads.md:30 src/async/async-await.md:25 msgid "Key points:" -msgstr "" +msgstr "Nøglepunkter:" #: src/cargo/rust-ecosystem.md:23 msgid "" @@ -1779,6 +1787,11 @@ msgid "" "}\n" "```" msgstr "" +"```rust,editable\n" +"fn main() {\n" +" println!(\"Rediger mig!\");\n" +"}\n" +"```" #: src/cargo/code-samples.md:19 msgid "" @@ -1786,6 +1799,8 @@ msgid "" "the\n" "text box." msgstr "" +"Du kan bruge Ctrl + Enter for at afvikle koden når fokus er\n" +"i tekstboksen." #: src/cargo/code-samples.md:24 msgid "" @@ -1822,16 +1837,21 @@ msgstr "" msgid "" "```shell\n" "% rustc --version\n" -"rustc 1.61.0 (fe5b13d68 2022-05-18)\n" +"rustc 1.69.0 (84c898d65 2023-04-16)\n" "% cargo --version\n" -"cargo 1.61.0 (a028ae4 2022-04-29)\n" +"cargo 1.69.0 (6e9a83356 2023-04-12)\n" "```" msgstr "" +"```shell\n" +"% rustc --version\n" +"rustc 1.69.0 (84c898d65 2023-04-16)\n" +"% cargo --version\n" +"cargo 1.69.0 (6e9a83356 2023-04-12)\n" +"```" #: src/cargo/running-locally.md:15 msgid "" -"With this is in place, then follow these steps to build a Rust binary from " -"one\n" +"With this in place, follow these steps to build a Rust binary from one\n" "of the examples in this training:" msgstr "" @@ -1902,13 +1922,15 @@ msgstr "" #: src/welcome-day-1.md:1 msgid "# Welcome to Day 1" -msgstr "" +msgstr "# Velkommen til Dag 1" #: src/welcome-day-1.md:3 msgid "" "This is the first day of Comprehensive Rust. We will cover a lot of ground\n" "today:" msgstr "" +"Dette er den første dag af Comprehensive Rust. Vi kommer til at dække en masse terræn\n" +"i dag:" #: src/welcome-day-1.md:6 msgid "" @@ -1933,11 +1955,12 @@ msgid "" "encouraged!\n" " * As an instructor, you should try to keep the discussions relevant, i." "e.,\n" -" keep the related to how Rust does things vs some other language. It can " -"be\n" -" hard to find the right balance, but err on the side of allowing " -"discussions\n" -" since they engage people much more than one-way communication.\n" +" keep the discussions related to how Rust does things vs some other " +"language. \n" +" It can be hard to find the right balance, but err on the side of " +"allowing \n" +" discussions since they engage people much more than one-way " +"communication.\n" "* The questions will likely mean that we talk about things ahead of the " "slides.\n" " * This is perfectly okay! Repetition is an important part of learning. " @@ -1976,7 +1999,7 @@ msgstr "" #: src/welcome-day-1/what-is-rust.md:1 msgid "# What is Rust?" -msgstr "" +msgstr "# Hvad er Rust?" #: src/welcome-day-1/what-is-rust.md:3 msgid "" @@ -2002,7 +2025,7 @@ msgstr "" #: src/welcome-day-1/what-is-rust.md:21 msgid "Rust fits in the same area as C++:" -msgstr "" +msgstr "Rust har det samme anvendelsesområde som C++:" #: src/welcome-day-1/what-is-rust.md:23 msgid "" @@ -2015,7 +2038,7 @@ msgstr "" #: src/hello-world.md:1 msgid "# Hello World!" -msgstr "" +msgstr "# Hej Verden!" #: src/hello-world.md:3 msgid "" @@ -2031,10 +2054,15 @@ msgid "" "}\n" "```" msgstr "" +"```rust,editable\n" +"fn main() {\n" +" println!(\"Hallo 🌍!\");\n" +"}\n" +"```" #: src/hello-world.md:12 msgid "What you see:" -msgstr "" +msgstr "Hvad du ser:" #: src/hello-world.md:14 msgid "" @@ -2069,17 +2097,17 @@ msgid "" "* Macros being 'hygienic' means they don't accidentally capture identifiers " "from\n" " the scope they are used in. Rust macros are actually only\n" -" [partially hygenic](https://veykril.github.io/tlborm/decl-macros/minutiae/" +" [partially hygienic](https://veykril.github.io/tlborm/decl-macros/minutiae/" "hygiene.html)." msgstr "" #: src/hello-world/small-example.md:1 msgid "# Small Example" -msgstr "" +msgstr "# Et lille eksempel" #: src/hello-world/small-example.md:3 msgid "Here is a small example program in Rust:" -msgstr "" +msgstr "Her er et lille eksempel på et program i Rust:" #: src/hello-world/small-example.md:5 msgid "" @@ -2134,7 +2162,7 @@ msgstr "" #: src/why-rust.md:1 msgid "# Why Rust?" -msgstr "" +msgstr "# Hvorfor bruge Rust?" #: src/why-rust.md:3 msgid "Some unique selling points of Rust:" @@ -2201,7 +2229,7 @@ msgstr "" #: src/why-rust/compile-time.md:19 msgid "" -"* You can for use [`Box::leak`] to leak a pointer. A use of this could\n" +"* You can use [`Box::leak`] to leak a pointer. A use of this could\n" " be to get runtime-initialized and runtime-sized static variables\n" "* You can use [`std::mem::forget`] to make the compiler \"forget\" about\n" " a value (meaning the destructor is never run).\n" @@ -2538,15 +2566,7 @@ msgid "" "over their lifetime." msgstr "" -#: src/basic-syntax/references.md:21 -msgid "" -"
\n" -"Key points:" -msgstr "" -"
\n" -"Nøglepunkter:" - -#: src/basic-syntax/references.md:24 +#: src/basic-syntax/references.md:25 msgid "" "* Be sure to note the difference between `let mut ref_x: &i32` and `let " "ref_x:\n" @@ -2915,10 +2935,6 @@ msgid "" "section." msgstr "" -#: src/basic-syntax/functions-interlude.md:30 -msgid "" -msgstr "" - #: src/exercises/day-1/morning.md:1 msgid "# Day 1: Morning Exercises" msgstr "# Dag 1: formiddagsøvelser" @@ -3322,7 +3338,7 @@ msgid "" msgstr "" #: src/basic-syntax/static-and-const.md:27 -msgid "According the the [Rust RFC Book][1] these are inlined upon use." +msgid "According to the [Rust RFC Book][1] these are inlined upon use." msgstr "" #: src/basic-syntax/static-and-const.md:29 @@ -4276,7 +4292,7 @@ msgstr "" #: src/ownership/lifetimes.md:5 msgid "" -"* The lifetime can be elided: `add(p1: &Point, p2: &Point) -> Point`.\n" +"* The lifetime can be implicit: `add(p1: &Point, p2: &Point) -> Point`.\n" "* Lifetimes can also be explicit: `&'a Point`, `&'document str`.\n" "* Read `&'a Point` as \"a borrowed `Point` which is valid for at least the\n" " lifetime `a`\".\n" @@ -4285,7 +4301,11 @@ msgid "" " yourself.\n" " * Lifetime annotations create constraints; the compiler verifies that " "there is\n" -" a valid solution." +" a valid solution.\n" +"* Lifetimes for function arguments and return values must be fully " +"specified,\n" +" but Rust allows these to be elidied in most cases with [a few simple\n" +" rules](https://doc.rust-lang.org/nomicon/lifetime-elision.html)." msgstr "" #: src/ownership/lifetimes-function-calls.md:1 @@ -4788,15 +4808,13 @@ msgid "" "```" msgstr "" -#: src/structs.md:29 -msgid "" -"
\n" -"Key Points: " +#: src/structs.md:31 src/enums.md:33 src/enums/sizes.md:29 src/methods.md:30 +#: src/methods/example.md:46 src/pattern-matching.md:25 +#: src/pattern-matching/match-guards.md:22 src/control-flow/blocks.md:42 +msgid "Key Points:" msgstr "" -"
\n" -"Nøglepunkter: " -#: src/structs.md:32 +#: src/structs.md:33 msgid "" "* Structs work like in C or C++.\n" " * Like in C++, and unlike in C, no typedef is needed to define a type.\n" @@ -4997,12 +5015,6 @@ msgid "" "```" msgstr "" -#: src/enums.md:33 src/enums/sizes.md:29 src/methods.md:30 -#: src/methods/example.md:46 src/pattern-matching.md:25 -#: src/pattern-matching/match-guards.md:22 src/control-flow/blocks.md:42 -msgid "Key Points:" -msgstr "" - #: src/enums.md:35 msgid "" "* Enumerations allow you to collect a set of values under one type\n" @@ -5342,15 +5354,14 @@ msgid "" " method becomes the owner of the object. The object will be dropped " "(deallocated)\n" " when the method returns, unless its ownership is explicitly\n" -" transmitted.\n" -"* `mut self`: same as above, but while the method owns the object, it can\n" -" mutate it too. Complete ownership does not automatically mean mutability.\n" +" transmitted. Complete ownership does not automatically mean mutability.\n" +"* `mut self`: same as above, but the method can mutate the object. \n" "* No receiver: this becomes a static method on the struct. Typically used " "to\n" " create constructors which are called `new` by convention." msgstr "" -#: src/methods/receiver.md:19 +#: src/methods/receiver.md:18 msgid "" "Beyond variants on `self`, there are also\n" "[special wrapper types](https://doc.rust-lang.org/reference/special-types-" @@ -5358,7 +5369,7 @@ msgid "" "allowed to be receiver types, such as `Box`." msgstr "" -#: src/methods/receiver.md:25 +#: src/methods/receiver.md:24 msgid "" "Consider emphasizing \"shared and immutable\" and \"unique and mutable\". " "These constraints always come\n" @@ -6768,15 +6779,21 @@ msgid "" msgstr "" "```bob\n" " Stak Heap\n" -".- - - - - - - - - - - - -. .- - - - - - - - - - - - - - - - - - - - - - - -.\n" +".- - - - - - - - - - - - -. .- - - - - - - - - - - - - - - - - - - - - - " +"- -.\n" ": : : :\n" -": list : : :\n" -": +------+----+----+ : : +------+----+----+ +------+----+----+ :\n" -": | Cons | 1 | o--+----+-----+--->| Cons | 2 | o--+--->| Nil | // | // | :\n" -": +------+----+----+ : : +------+----+----+ +------+----+----+ :\n" +": " +"list : : :\n" +": +------+----+----+ : : +------+----+----+ +------+----+----" +"+ :\n" +": | Cons | 1 | o--+----+-----+--->| Cons | 2 | o--+--->| Nil | // | // " +"| :\n" +": +------+----+----+ : : +------+----+----+ +------+----+----" +"+ :\n" ": : : :\n" ": : : :\n" -"'- - - - - - - - - - - - -' '- - - - - - - - - - - - - - - - - - - - - - - -'\n" +"'- - - - - - - - - - - - -' '- - - - - - - - - - - - - - - - - - - - - - " +"- -'\n" "```" #: src/std/box-recursive.md:33 @@ -6830,15 +6847,21 @@ msgid "" msgstr "" "```bob\n" " Stak Heap\n" -".- - - - - - - - - - - - -. .- - - - - - - - - - - - - - - - - - - - - - -.\n" +".- - - - - - - - - - - - -. .- - - - - - - - - - - - - - - - - - - - - - " +"-.\n" ": : : :\n" -": list : : :\n" -": +----+----+ : : +----+----+ +----+------+ :\n" -": | 1 | o--+-----------+-----+--->| 2 | o--+--->| // | null | :\n" -": +----+----+ : : +----+----+ +----+------+ :\n" +": " +"list : : :\n" +": +----+----+ : : +----+----+ +----+------" +"+ :\n" +": | 1 | o--+-----------+-----+--->| 2 | o--+--->| // | null " +"| :\n" +": +----+----+ : : +----+----+ +----+------" +"+ :\n" ": : : :\n" ": : : :\n" -"`- - - - - - - - - - - - -' '- - - - - - - - - - - - - - - - - - - - - - -'\n" +"`- - - - - - - - - - - - -' '- - - - - - - - - - - - - - - - - - - - - - " +"-'\n" "```" #: src/std/rc.md:1 @@ -7654,29 +7677,50 @@ msgid "" msgstr "" "```bob\n" " Stak Heap\n" -".- - - - - - - - - - - - - -. .- - - - - - - - - - - - - - - - - - - - - - -.\n" +".- - - - - - - - - - - - - -. .- - - - - - - - - - - - - - - - - - - - - " +"- -.\n" ": : : :\n" -": pets : : :\n" -": +-----------+-------+ : : +-----+-----+ :\n" -": | ptr | o---+---+-----+-->| o o | o o | :\n" -": | len | 2 | : : +-|-|-+-|-|-+ :\n" -": | capacity | 2 | : : | | | | +---------------+ :\n" -": +-----------+-------+ : : | | | '-->| name: \"Fido\" | :\n" -": : : | | | +---------------+ :\n" -"`- - - - - - - - - - - - - -' : | | | :\n" -" : | | | +----------------------+ :\n" -" : | | '---->| \"::name\" | :\n" -" : | | +----------------------+ :\n" -" : | | :\n" -" : | | +-+ :\n" -" : | '-->|\\| :\n" -" : | +-+ :\n" -" : | :\n" -" : | +----------------------+ :\n" -" : '---->| \"::name\" | :\n" -" : +----------------------+ :\n" +": " +"pets : : :\n" +": +-----------+-------+ : : +-----+-----" +"+ :\n" +": | ptr | o---+---+-----+-->| o o | o o " +"| :\n" +": | len | 2 | : : +-|-|-+-|-|-" +"+ :\n" +": | capacity | 2 | : : | | | | +---------------" +"+ :\n" +": +-----------+-------+ : : | | | '-->| name: \"Fido\" " +"| :\n" +": : : | | | +---------------" +"+ :\n" +"`- - - - - - - - - - - - - -' : | | " +"| :\n" +" : | | | +----------------------" +"+ :\n" +" : | | '---->| \"::name\" " +"| :\n" +" : | | +----------------------" +"+ :\n" +" : | " +"| :\n" +" : | | +-" +"+ :\n" +" : | '-->|" +"\\| :\n" +" : | +-" +"+ :\n" +" : " +"| :\n" +" : | +----------------------" +"+ :\n" +" : '---->| \"::name\" " +"| :\n" +" : +----------------------" +"+ :\n" " : :\n" -" '- - - - - - - - - - - - - - - - - - - - - - -'\n" +" '- - - - - - - - - - - - - - - - - - - - - " +"- -'\n" "\n" "```" @@ -7774,10 +7818,29 @@ msgid "" "* Move method `not_equal` to a new trait `NotEqual`.\n" "\n" "* Make `NotEqual` a super trait for `Equal`.\n" +" ```rust,editable,compile_fail\n" +" trait NotEqual: Equals {\n" +" fn not_equal(&self, other: &Self) -> bool {\n" +" !self.equal(other)\n" +" }\n" +" }\n" +" ```\n" "\n" "* Provide a blanket implementation of `NotEqual` for `Equal`.\n" +" ```rust,editable,compile_fail\n" +" trait NotEqual {\n" +" fn not_equal(&self, other: &Self) -> bool;\n" +" }\n" +"\n" +" impl NotEqual for T where T: Equals {\n" +" fn not_equal(&self, other: &Self) -> bool {\n" +" !self.equal(other)\n" +" }\n" +" }\n" +" ```\n" " * With the blanket implementation, you no longer need `NotEqual` as a " -"super trait for `Equal`." +"super trait for `Equal`.\n" +" " msgstr "" #: src/traits/trait-bounds.md:1 @@ -9923,7 +9986,7 @@ msgstr "" msgid "" "```shell\n" "$ m hello_rust\n" -"$ adb push $ANDROID_PRODUCT_OUT/system/bin/hello_rust /data/local/tmp\n" +"$ adb push \"$ANDROID_PRODUCT_OUT/system/bin/hello_rust /data/local/tmp\"\n" "$ adb shell /data/local/tmp/hello_rust\n" "Hello from Rust!\n" "```" @@ -10009,8 +10072,8 @@ msgstr "" msgid "" "```shell\n" "$ m hello_rust_with_dep\n" -"$ adb push $ANDROID_PRODUCT_OUT/system/bin/hello_rust_with_dep /data/local/" -"tmp\n" +"$ adb push \"$ANDROID_PRODUCT_OUT/system/bin/hello_rust_with_dep /data/local/" +"tmp\"\n" "$ adb shell /data/local/tmp/hello_rust_with_dep\n" "Hello Bob, it is very\n" "nice to meet you!\n" @@ -10211,7 +10274,8 @@ msgstr "" msgid "" "```shell\n" "$ m birthday_server\n" -"$ adb push $ANDROID_PRODUCT_OUT/system/bin/birthday_server /data/local/tmp\n" +"$ adb push \"$ANDROID_PRODUCT_OUT/system/bin/birthday_server /data/local/" +"tmp\"\n" "$ adb shell /data/local/tmp/birthday_server\n" "```" msgstr "" @@ -10324,7 +10388,8 @@ msgstr "" msgid "" "```shell\n" "$ m birthday_client\n" -"$ adb push $ANDROID_PRODUCT_OUT/system/bin/birthday_client /data/local/tmp\n" +"$ adb push \"$ANDROID_PRODUCT_OUT/system/bin/birthday_client /data/local/" +"tmp\"\n" "$ adb shell /data/local/tmp/birthday_client Charlie 60\n" "Happy Birthday Charlie, congratulations with the 60 years!\n" "```" @@ -10420,7 +10485,8 @@ msgstr "" msgid "" "```shell\n" "$ m hello_rust_logs\n" -"$ adb push $ANDROID_PRODUCT_OUT/system/bin/hello_rust_logs /data/local/tmp\n" +"$ adb push \"$ANDROID_PRODUCT_OUT/system/bin/hello_rust_logs /data/local/" +"tmp\"\n" "$ adb shell /data/local/tmp/hello_rust_logs\n" "```" msgstr "" @@ -10657,8 +10723,8 @@ msgstr "" msgid "" "```shell\n" "$ m print_birthday_card\n" -"$ adb push $ANDROID_PRODUCT_OUT/system/bin/print_birthday_card /data/local/" -"tmp\n" +"$ adb push \"$ANDROID_PRODUCT_OUT/system/bin/print_birthday_card /data/local/" +"tmp\"\n" "$ adb shell /data/local/tmp/print_birthday_card\n" "```" msgstr "" @@ -10795,7 +10861,8 @@ msgstr "" msgid "" "```shell\n" "$ m analyze_numbers\n" -"$ adb push $ANDROID_PRODUCT_OUT/system/bin/analyze_numbers /data/local/tmp\n" +"$ adb push \"$ANDROID_PRODUCT_OUT/system/bin/analyze_numbers /data/local/" +"tmp\"\n" "$ adb shell /data/local/tmp/analyze_numbers\n" "```" msgstr "" @@ -11026,7 +11093,8 @@ msgid "" "```" msgstr "" "```bash\n" -"sudo apt install gcc-aarch64-linux-gnu gdb-multiarch libudev-dev picocom pkg-config qemu-system-arm\n" +"sudo apt install gcc-aarch64-linux-gnu gdb-multiarch libudev-dev picocom pkg-" +"config qemu-system-arm\n" "rustup update\n" "rustup target add aarch64-unknown-none thumbv7em-none-eabihf\n" "rustup component add llvm-tools-preview\n" @@ -11048,7 +11116,8 @@ msgid "" "```" msgstr "" "```bash\n" -"echo 'SUBSYSTEM==\"usb\", ATTR{idVendor}==\"0d28\", MODE=\"0664\", GROUP=\"plugdev\"' |\\\n" +"echo 'SUBSYSTEM==\"usb\", ATTR{idVendor}==\"0d28\", MODE=\"0664\", " +"GROUP=\"plugdev\"' |\\\n" " sudo tee /etc/udev/rules.d/50-microbit.rules\n" "sudo udevadm control --reload-rules\n" "```" @@ -11913,7 +11982,6 @@ msgid "" msgstr "" #: src/exercises/bare-metal/compass.md:26 src/exercises/bare-metal/rtc.md:19 -#: src/exercises/concurrency/elevator.md:17 msgid "`src/main.rs`:" msgstr "`src/main.rs`:" @@ -11921,7 +11989,6 @@ msgstr "`src/main.rs`:" #: src/exercises/concurrency/dining-philosophers.md:17 #: src/exercises/concurrency/link-checker.md:55 #: src/exercises/concurrency/dining-philosophers-async.md:11 -#: src/exercises/concurrency/elevator.md:19 msgid "" msgstr "" @@ -11963,7 +12030,6 @@ msgid "" msgstr "" #: src/exercises/bare-metal/compass.md:64 src/exercises/bare-metal/rtc.md:385 -#: src/exercises/concurrency/elevator.md:365 msgid "`Cargo.toml` (you shouldn't need to change this):" msgstr "" @@ -11971,7 +12037,7 @@ msgstr "" #: src/exercises/concurrency/dining-philosophers.md:63 #: src/exercises/concurrency/link-checker.md:35 #: src/exercises/concurrency/dining-philosophers-async.md:60 -#: src/exercises/concurrency/elevator.md:367 +#: src/exercises/concurrency/chat-app.md:17 msgid "" msgstr "" @@ -14259,7 +14325,8 @@ msgstr "" "\t$(OBJCOPY) -O binary target/aarch64-unknown-none/debug/rtc $@\n" "\n" "qemu: rtc.bin\n" -"\tqemu-system-aarch64 -machine virt,gic-version=3 -cpu max -serial mon:stdio -display none -kernel $< -s\n" +"\tqemu-system-aarch64 -machine virt,gic-version=3 -cpu max -serial mon:stdio " +"-display none -kernel $< -s\n" "\n" "clean:\n" "\tcargo clean\n" @@ -14353,7 +14420,8 @@ msgid "" "* Thread panics are independent of each other.\n" " * Panics can carry a payload, which can be unpacked with `downcast_ref`." msgstr "" -"* Tråde er alle dæmontråde (eng: _daemon threads_), hvilket vil sige at hovedtråden ikke venter på dem.\n" +"* Tråde er alle dæmontråde (eng: _daemon threads_), hvilket vil sige at " +"hovedtråden ikke venter på dem.\n" "* Hver tråd kan gå i panik uafhængigt af andre tråde.\n" " * En panik kan have en nyttelast som kan udpakkes med `downcast_ref`." @@ -14375,11 +14443,13 @@ msgid "" msgstr "" "* Bemærk at tråden stopper før den når 10 --- hovedtråden venter ikke.\n" "\n" -"* Brug `let handle = thread::spawn(...)` og senere `handle.join()` for at vente på at tråden afsluttes.\n" +"* Brug `let handle = thread::spawn(...)` og senere `handle.join()` for at " +"vente på at tråden afsluttes.\n" "\n" "* Skab en panik i tråden, bemærk hvordan dette ikke påvirker `main`.\n" "\n" -"* Bruge `Result`-returværdien fra `handle.join()` til at få adgang til panikkens nyttelast.\n" +"* Bruge `Result`-returværdien fra `handle.join()` til at få adgang til " +"panikkens nyttelast.\n" " Dette er et godt tidspunkt til at snakke om [`Any`]." #: src/concurrency/scoped-threads.md:1 @@ -14418,7 +14488,9 @@ msgstr "" #: src/concurrency/scoped-threads.md:17 msgid "However, you can use a [scoped thread][1] for this:" -msgstr "Du kan dog bruge en [tråd med virkefelt (eng: _scoped thread_)][1] for at opnå dette:" +msgstr "" +"Du kan dog bruge en [tråd med virkefelt (eng: _scoped thread_)][1] for at " +"opnå dette:" #: src/concurrency/scoped-threads.md:19 msgid "" @@ -14459,8 +14531,12 @@ msgid "" "thread, or immutably by any number of threads.\n" " " msgstr "" -"* Grunden er, at `thread::scope`-funktionen garanterer at alle trådene er blevet forenet med hovedtråden når kaldet afsluttet. De vil således returnere det lånte data.\n" -"* De normale låneregler for Rust gælder: du kan enten lade én tråd låne data for at ændre på det, eller du kan lade flere tråde låne data uden at ændre på det." +"* Grunden er, at `thread::scope`-funktionen garanterer at alle trådene er " +"blevet forenet med hovedtråden når kaldet afsluttet. De vil således " +"returnere det lånte data.\n" +"* De normale låneregler for Rust gælder: du kan enten lade én tråd låne data " +"for at ændre på det, eller du kan lade flere tråde låne data uden at ændre " +"på det." #: src/concurrency/channels.md:1 msgid "# Channels" @@ -14514,6 +14590,7 @@ msgid "You get an unbounded and asynchronous channel with `mpsc::channel()`:" msgstr "" #: src/concurrency/channels/unbounded.md:5 +#, fuzzy msgid "" "```rust,editable\n" "use std::sync::mpsc;\n" @@ -14534,11 +14611,29 @@ msgid "" " thread::sleep(Duration::from_millis(100));\n" "\n" " for msg in rx.iter() {\n" -" println!(\"Main: got {}\", msg);\n" +" println!(\"Main: got {msg}\");\n" " }\n" "}\n" "```" msgstr "" +"```rust,editable\n" +"use std::thread;\n" +"use std::time::Duration;\n" +"\n" +"fn main() {\n" +" thread::spawn(|| {\n" +" for i in 1..10 {\n" +" println!(\"Tæller i tråden: {i}!\");\n" +" thread::sleep(Duration::from_millis(5));\n" +" }\n" +" });\n" +"\n" +" for i in 1..5 {\n" +" println!(\"Hovedtråden: {i}\");\n" +" thread::sleep(Duration::from_millis(5));\n" +" }\n" +"}\n" +"```" #: src/concurrency/channels/bounded.md:1 msgid "# Bounded Channels" @@ -15201,6 +15296,7 @@ msgid "" msgstr "" #: src/exercises/concurrency/link-checker.md:106 +#: src/exercises/concurrency/chat-app.md:140 msgid "## Tasks" msgstr "## Opgaver" @@ -16139,8 +16235,8 @@ msgid "" "time\n" " you are going to implement it with Async Rust.\n" "\n" -"* The Elevator Problem: this is a larger project that allows you experiment\n" -" with more advanced Async Rust features and some of its pitfalls!" +"* A Broadcast Chat Application: this is a larger project that allows you\n" +" experiment with more advanced Async Rust features." msgstr "" #: src/exercises/concurrency/dining-philosophers-async.md:1 @@ -16241,496 +16337,228 @@ msgstr "" msgid "* Can you make your implementation single-threaded? " msgstr "" -#: src/exercises/concurrency/elevator.md:1 -msgid "# Elevator Operation" +#: src/exercises/concurrency/chat-app.md:1 +msgid "# Broadcast Chat Application" msgstr "" -#: src/exercises/concurrency/elevator.md:3 +#: src/exercises/concurrency/chat-app.md:3 msgid "" -"Elevators seem simple. You press a button, doors open, you wait, and you're " -"at\n" -"the floor you requested. But implementing an elevator controller is " -"surprisingly\n" -"difficult! This exercise involves building a simple elevator control that\n" -"operates in a simple simulator." +"In this exercise, we want to use our new knowledge to implement a broadcast\n" +"chat application. We have a chat server that the clients connect to and " +"publish\n" +"their messages. The client reads user messages from the standard input, and\n" +"sends them to the server. The chat server broadcasts each message that it\n" +"receives to all the clients." msgstr "" -#: src/exercises/concurrency/elevator.md:8 +#: src/exercises/concurrency/chat-app.md:9 msgid "" -"The overall design of this elevator uses the actor pattern: you will " -"implement a\n" -"controller task that communicates with other components of the elevator " -"system\n" -"by sending and receiving messages." +"For this, we use [a broadcast channel][1] on the server, and\n" +"[`tokio_websockets`][2] for the communication between the client and the\n" +"server." msgstr "" -#: src/exercises/concurrency/elevator.md:12 -msgid "## Getting Started" -msgstr "## Kom godt igang" - -#: src/exercises/concurrency/elevator.md:14 -msgid "" -"Download the [exercise template](../../comprehensive-rust-exercises.zip) and " -"look in the `elevator`\n" -"directory for the following files." +#: src/exercises/concurrency/chat-app.md:13 +msgid "Create a new Cargo project and add the following dependencies:" msgstr "" -#: src/exercises/concurrency/elevator.md:21 -msgid "" -"```rust,compile_fail\n" -"use building::BuildingEvent;\n" -"use tokio::sync::broadcast;\n" -"\n" -"mod building;\n" -"mod controller;\n" -"mod driver;\n" -"\n" -"#[tokio::main]\n" -"async fn main() {\n" -" let building = driver::make_building();\n" -" let (building_task, events_rx, building_cmd_tx, driver_cmd_tx) = " -"building.start();\n" -"\n" -" tokio::spawn(print_events(events_rx.resubscribe()));\n" -" tokio::spawn(driver::driver(events_rx.resubscribe(), driver_cmd_tx));\n" -" tokio::spawn(controller::controller(events_rx, building_cmd_tx));\n" -" building_task.await.unwrap();\n" -"}\n" -"\n" -"async fn print_events(mut events_rx: broadcast::Receiver) {\n" -" while let Ok(evt) = events_rx.recv().await {\n" -" println!(\"BuildingEvent::{:?}\", evt);\n" -" }\n" -"}\n" -"```" +#: src/exercises/concurrency/chat-app.md:15 +msgid "`Cargo.toml`:" msgstr "" -#: src/exercises/concurrency/elevator.md:47 -msgid "`src/building.rs`:" -msgstr "" - -#: src/exercises/concurrency/elevator.md:49 -msgid "" -msgstr "" - -#: src/exercises/concurrency/elevator.md:51 -msgid "" -"```rust,compile_fail\n" -"//! The building simulates floors and elevators.\n" -"\n" -"use tokio::sync::{broadcast, mpsc};\n" -"use tokio::task;\n" -"use tokio::time;\n" -"\n" -"#[derive(Debug, Clone)]\n" -"pub enum Direction {\n" -" Up,\n" -" Down,\n" -"}\n" -"\n" -"/// A passenger is a person with a destination floor in mind.\n" -"#[derive(Debug)]\n" -"struct Passenger {\n" -" destination: FloorId,\n" -"}\n" -"\n" -"/// FloorId identifies a floor. These are zero-based integers.\n" -"pub type FloorId = usize;\n" -"\n" -"/// Floor represents the current status of a floor in the building.\n" -"#[derive(Default, Debug)]\n" -"struct Floor {\n" -" passengers: Vec,\n" -"}\n" -"\n" -"/// ElevatorId identifies an elevator in the building. These are zero-based " -"integers.\n" -"pub type ElevatorId = usize;\n" -"\n" -"/// Elevator represents the current status of an elevator in the building.\n" -"#[derive(Default, Debug)]\n" -"struct Elevator {\n" -" /// Floor the elevator is currently on. In the simulation the elevator\n" -" /// transports instantaneously from one floor to the next in a single\n" -" /// simulation tick.\n" -" position: FloorId,\n" -" /// Destination floor for the elevator, if any. This can change at any " -"time.\n" -" destination: Option,\n" -" /// Passengers currently on the elevator.\n" -" passengers: Vec,\n" -" /// True if the elevator is stopped with the doors open. The elevator\n" -" /// will not move with the doors open, but they will close at the next\n" -" /// tick of the simulation.\n" -" doors_open: bool,\n" -"}\n" -"\n" -"/// A BuildingEvent is an event that occurs in the building.\n" -"#[derive(Debug, Clone)]\n" -"pub enum BuildingEvent {\n" -" /// A passenger has pressed a floor button in the elevator.\n" -" FloorButtonPressed(ElevatorId, FloorId),\n" -" /// A passenger on the given floor has pressed the call button.\n" -" CallButtonPressed(FloorId, Direction),\n" -" /// The elevator has arrived at the given floor. If this is the\n" -" /// elevator's destination, then it will stop open its doors.\n" -" AtFloor(ElevatorId, FloorId),\n" -" /// A passenger has been delivered to their desired floor.\n" -" PassengerDelivered(FloorId),\n" -"}\n" -"\n" -"/// A BuildingCommand tells the building what to do.\n" -"#[derive(Debug)]\n" -"pub enum BuildingCommand {\n" -" /// Set the elevator's destination. The elevator will close its doors\n" -" /// if necessary and then begin moving toward this floor.\n" -" GoToFloor(ElevatorId, FloorId),\n" -"}\n" -"\n" -"/// A DriverCommand is a message from the driver to change the state of\n" -"/// the building.\n" -"#[derive(Debug)]\n" -"pub enum DriverCommand {\n" -" /// A passenger has arrived and is waiting for an elevator. The " -"passenger will automatically\n" -" /// press the relevant call button, board the elevator when it arrives, " -"press their floor\n" -" /// button, and depart when the doors open on their destination floor.\n" -" PassengerArrived { at: FloorId, destination: FloorId },\n" -"\n" -" /// Halt all activity in the building and end the building task.\n" -" Halt,\n" -"}\n" -"\n" -"/// Building manages the current status of the building.\n" -"#[derive(Debug)]\n" -"pub struct Building {\n" -" floors: Vec,\n" -" elevators: Vec,\n" -"}\n" -"\n" -"impl Building {\n" -" pub fn new(num_floors: usize, num_elevators: usize) -> Self {\n" -" let mut floors = vec![];\n" -" for _ in 0..num_floors {\n" -" floors.push(Floor::default());\n" -" }\n" -" let mut elevators = vec![];\n" -" for _ in 0..num_elevators {\n" -" elevators.push(Elevator::default());\n" -" }\n" -" Self { floors, elevators }\n" -" }\n" -"\n" -" /// Start the building. The resulting channels are used to communicate\n" -" /// with the building\n" -" pub fn start(\n" -" self,\n" -" ) -> (\n" -" task::JoinHandle<()>,\n" -" broadcast::Receiver,\n" -" mpsc::Sender,\n" -" mpsc::Sender,\n" -" ) {\n" -" let (events_tx, events_rx) = broadcast::channel(10);\n" -" let (building_cmd_tx, building_cmd_rx) = mpsc::channel(10);\n" -" let (driver_cmd_tx, driver_cmd_rx) = mpsc::channel(10);\n" -" let task = tokio::spawn(self.run(events_tx, building_cmd_rx, " -"driver_cmd_rx));\n" -" (task, events_rx, building_cmd_tx, driver_cmd_tx)\n" -" }\n" -"\n" -" async fn run(\n" -" mut self,\n" -" events_tx: broadcast::Sender,\n" -" mut building_cmd_rx: mpsc::Receiver,\n" -" mut driver_cmd_rx: mpsc::Receiver,\n" -" ) {\n" -" let mut ticker = time::interval(time::Duration::from_millis(100));\n" -" loop {\n" -" tokio::select! {\n" -" Some(BuildingCommand::GoToFloor(el, fl)) = building_cmd_rx." -"recv() => {\n" -" self.elevators[el].destination = Some(fl);\n" -" }\n" -" Some(cmd) = driver_cmd_rx.recv() => {\n" -" match cmd {\n" -" DriverCommand::PassengerArrived{at, destination} => " -"{\n" -" self.new_passenger(&events_tx, at, destination)." -"await;\n" -" }\n" -" DriverCommand::Halt => return,\n" -" }\n" -" }\n" -" _ = ticker.tick() => self.move_elevators(&events_tx).await\n" -" }\n" -" }\n" -" }\n" -"\n" -" /// Move the elevators toward their destinations.\n" -" async fn move_elevators(&mut self, events_tx: &broadcast::" -"Sender) {\n" -" for el in 0..self.elevators.len() {\n" -" let elevator = &mut self.elevators[el];\n" -"\n" -" // If the elevator's doors are open, close them and wait for the " -"next tick.\n" -" if elevator.doors_open {\n" -" elevator.doors_open = false;\n" -" continue;\n" -" }\n" -"\n" -" // If the elevator has somewhere to go, move toward it.\n" -" if let Some(dest) = elevator.destination {\n" -" if dest > elevator.position {\n" -" elevator.position += 1;\n" -" }\n" -" if dest < elevator.position {\n" -" elevator.position -= 1;\n" -" }\n" -" events_tx\n" -" .send(BuildingEvent::AtFloor(el, elevator.position))\n" -" .unwrap();\n" -"\n" -" // If the elevator has reached its destination, open\n" -" // the doors and let passengers get on and off.\n" -" if elevator.position == dest {\n" -" elevator.destination = None;\n" -" elevator.doors_open = true;\n" -" self.exchange_passengers(&events_tx, el).await;\n" -" }\n" -" }\n" -" }\n" -" }\n" -"\n" -" /// Handle a new passenger arriving at the given floor.\n" -" async fn new_passenger(\n" -" &mut self,\n" -" events_tx: &broadcast::Sender,\n" -" at: FloorId,\n" -" destination: FloorId,\n" -" ) {\n" -" println!(\"Passenger arrived at {} going to {}\", at, destination);\n" -" if at == destination {\n" -" events_tx\n" -" .send(BuildingEvent::PassengerDelivered(destination))\n" -" .unwrap();\n" -" return;\n" -" }\n" -"\n" -" self.floors[at].passengers.push(Passenger { destination });\n" -" let dir = if at < destination {\n" -" Direction::Up\n" -" } else {\n" -" Direction::Down\n" -" };\n" -" events_tx\n" -" .send(BuildingEvent::CallButtonPressed(at, dir))\n" -" .unwrap();\n" -" }\n" -"\n" -" /// The doors for the given elevator are open, so take on and discharge " -"passengers.\n" -" async fn exchange_passengers(\n" -" &mut self,\n" -" events_tx: &broadcast::Sender,\n" -" el: ElevatorId,\n" -" ) {\n" -" let elevator = &mut self.elevators[el];\n" -" let fl = elevator.position;\n" -"\n" -" // Handle passengers leaving the elevator at their floor.\n" -" let (this_floor, other_floors): (Vec, Vec) = " -"elevator\n" -" .passengers\n" -" .drain(..)\n" -" .partition(|px| px.destination == fl);\n" -" for px in this_floor {\n" -" events_tx\n" -" .send(BuildingEvent::PassengerDelivered(px.destination))\n" -" .unwrap();\n" -" }\n" -" elevator.passengers = other_floors;\n" -"\n" -" // Handle passengers entering the elevator.\n" -" for px in self.floors[fl].passengers.drain(..) {\n" -" events_tx\n" -" .send(BuildingEvent::FloorButtonPressed(el, px." -"destination))\n" -" .unwrap();\n" -" elevator.passengers.push(px);\n" -" }\n" -" }\n" -"}\n" -"```" -msgstr "" - -#: src/exercises/concurrency/elevator.md:288 -msgid "`src/driver.rs`:" -msgstr "" - -#: src/exercises/concurrency/elevator.md:290 -msgid "" -msgstr "" - -#: src/exercises/concurrency/elevator.md:292 -msgid "" -"```rust,compile_fail\n" -"//! The driver controls when and where passengers arrive.\n" -"\n" -"use crate::building::{Building, BuildingEvent, DriverCommand};\n" -"use tokio::sync::{broadcast, mpsc};\n" -"\n" -"/// Create a new building to be driven by this driver.\n" -"pub fn make_building() -> Building {\n" -" Building::new(3, 1)\n" -"}\n" -"\n" -"/// Simulate people arriving at the ground floor and going to the first " -"floor, one by one.\n" -"pub async fn driver(\n" -" mut events_rx: broadcast::Receiver,\n" -" driver_cmd_tx: mpsc::Sender,\n" -") {\n" -" for _ in 0..3 {\n" -" // A passenger has arrived..\n" -" driver_cmd_tx\n" -" .send(DriverCommand::PassengerArrived {\n" -" at: 0,\n" -" destination: 2,\n" -" })\n" -" .await\n" -" .unwrap();\n" -"\n" -" // Wait until they are delivered..\n" -" while let Ok(evt) = events_rx.recv().await {\n" -" if let BuildingEvent::PassengerDelivered(_) = evt {\n" -" break;\n" -" }\n" -" }\n" -" }\n" -"\n" -" driver_cmd_tx.send(DriverCommand::Halt).await.unwrap();\n" -"}\n" -"```" -msgstr "" - -#: src/exercises/concurrency/elevator.md:330 -msgid "`src/controller.rs`:" -msgstr "" - -#: src/exercises/concurrency/elevator.md:332 -msgid "" -msgstr "" - -#: src/exercises/concurrency/elevator.md:334 -msgid "" -"```rust,compile_fail\n" -"//! The controller directs the elevators to operate so that passengers\n" -"//! get to their destinations.\n" -"\n" -"use crate::building::{BuildingCommand, BuildingEvent};\n" -"use tokio::sync::{broadcast, mpsc};\n" -"\n" -"pub async fn controller(\n" -" mut events_rx: broadcast::Receiver,\n" -" building_cmd_tx: mpsc::Sender,\n" -") {\n" -" while let Ok(evt) = events_rx.recv().await {\n" -" match evt {\n" -" BuildingEvent::CallButtonPressed(at, _) => {\n" -" building_cmd_tx\n" -" .send(BuildingCommand::GoToFloor(0, at))\n" -" .await\n" -" .unwrap();\n" -" }\n" -" BuildingEvent::FloorButtonPressed(_, destination) => {\n" -" building_cmd_tx\n" -" .send(BuildingCommand::GoToFloor(0, destination))\n" -" .await\n" -" .unwrap();\n" -" }\n" -" _ => {}\n" -" }\n" -" }\n" -"}\n" -"```" -msgstr "" - -#: src/exercises/concurrency/elevator.md:369 +#: src/exercises/concurrency/chat-app.md:19 msgid "" "```toml\n" -"[workspace]\n" -"\n" "[package]\n" -"name = \"elevator\"\n" +"name = \"chat-async\"\n" "version = \"0.1.0\"\n" "edition = \"2021\"\n" "\n" "[dependencies]\n" -"tokio = { version = \"1.26.0\", features = [\"full\"] }\n" +"futures-util = \"0.3.28\"\n" +"http = \"0.2.9\"\n" +"tokio = { version = \"1.28.1\", features = [\"full\"] }\n" +"tokio-websockets = \"0.3.2\"\n" "```" msgstr "" -#: src/exercises/concurrency/elevator.md:381 -msgid "Use `cargo run` to run the elevator simulation." +#: src/exercises/concurrency/chat-app.md:32 +msgid "## The required APIs" msgstr "" -#: src/exercises/concurrency/elevator.md:383 -msgid "## Exercises" -msgstr "## Øvelser" - -#: src/exercises/concurrency/elevator.md:385 +#: src/exercises/concurrency/chat-app.md:33 msgid "" -"Begin by implementing a controller that can transport the passengers " -"provided by\n" -"the simple driver. There is only one elevator, and passengers always go " -"from\n" -"floor 0 to floor 2, one-by-one." -msgstr "" - -#: src/exercises/concurrency/elevator.md:389 -msgid "" -"Once you have this done, make the problem more complex. Suggested tasks:" -msgstr "" - -#: src/exercises/concurrency/elevator.md:391 -msgid "" -" * Make the driver more complex, with passengers arriving at random floors " -"with\n" -" random destinations at random times.\n" -"\n" -" * Create a building with more than one elevator, and adjust the controller " -"to\n" -" handle this efficiently.\n" -"\n" -" * Add additional events and metadata to analyze your controller's " -"efficiency.\n" -" What is the distribution of wait time for passengers? Is the result " -"fair?\n" -"\n" -" * Modify the building to support a maximum passenger capacity for each\n" -" elevator, and modify the controller to take this information into " -"account.\n" -"\n" -" * Update the driver to simulate business traffic, with lots of passengers " -"going\n" -" up from the ground floor at the same time, and those passengers returning " -"to\n" -" the ground floor some time later. Can your controller adjust to these\n" -" circumstances?\n" -"\n" -" * Modify the building to support \"destination dispatch\", where " -"passengers\n" -" signal their destination floor in the elevator lobby, before boarding " +"You are going to need the following functions from `tokio` and\n" +"[`tokio_websockets`][2]. Spend a few minutes to familiarize yourself with " "the\n" -" elevator.\n" +"API. " +msgstr "" + +#: src/exercises/concurrency/chat-app.md:37 +msgid "" +"- [WebsocketStream::next()][3]: for asynchronously reading messages from a\n" +" Websocket Stream.\n" +"- [SinkExt::send()][4] implemented by `WebsocketStream`: for asynchronously\n" +" sending messages on a Websocket Stream.\n" +"- [BufReader::read_line()][5]: for asynchronously reading user messages\n" +" from the standard input.\n" +"- [Sender::subscribe()][6]: for subscribing to a broadcast channel." +msgstr "" + +#: src/exercises/concurrency/chat-app.md:46 +msgid "## Two binaries" +msgstr "" + +#: src/exercises/concurrency/chat-app.md:48 +msgid "" +"Normally in a Cargo project, you can have only one binary, and one\n" +"`src/main.rs` file. In this project, we need two binaries. One for the " +"client,\n" +"and one for the server. You could potentially make them two separate Cargo\n" +"projects, but we are going to put them in a single Cargo project with two\n" +"binaries. For this to work, the client and the server code should go under\n" +"`src/bin` (see the [documentation][7]). " +msgstr "" + +#: src/exercises/concurrency/chat-app.md:55 +msgid "" +"Copy the following server and client code into `src/bin/server.rs` and\n" +"`src/bin/client.rs`, respectively. Your task is to complete these files as\n" +"described below. " +msgstr "" + +#: src/exercises/concurrency/chat-app.md:59 +#: src/exercises/concurrency/solutions-afternoon.md:117 +#, fuzzy +msgid "`src/bin/server.rs`:" +msgstr "`src/main.rs`:" + +#: src/exercises/concurrency/chat-app.md:61 +#, fuzzy +msgid "" +msgstr "" + +#: src/exercises/concurrency/chat-app.md:63 +msgid "" +"```rust,compile_fail\n" +"use futures_util::sink::SinkExt;\n" +"use std::error::Error;\n" +"use std::net::SocketAddr;\n" +"use tokio::net::{TcpListener, TcpStream};\n" +"use tokio::sync::broadcast::{channel, Sender};\n" +"use tokio_websockets::{Message, ServerBuilder, WebsocketStream};\n" "\n" -" * If you are taking the course with other students, trade controllers or\n" -" drivers with another student to see how robust your design is.\n" +"async fn handle_connection(\n" +" addr: SocketAddr,\n" +" mut ws_stream: WebsocketStream,\n" +" bcast_tx: Sender,\n" +") -> Result<(), Box> {\n" "\n" -" * Build a textual or graphical display of the elevators as they run." +" // TODO: For a hint, see the description of the task below.\n" +"\n" +"}\n" +"\n" +"#[tokio::main]\n" +"async fn main() -> Result<(), Box> {\n" +" let (bcast_tx, _) = channel(16);\n" +"\n" +" let listener = TcpListener::bind(\"127.0.0.1:2000\").await?;\n" +" println!(\"listening on port 2000\");\n" +"\n" +" loop {\n" +" let (socket, addr) = listener.accept().await?;\n" +" println!(\"New connection from {addr:?}\");\n" +" let bcast_tx = bcast_tx.clone();\n" +" tokio::spawn(async move {\n" +" // Wrap the raw TCP stream into a websocket.\n" +" let ws_stream = ServerBuilder::new().accept(socket).await?;\n" +"\n" +" handle_connection(addr, ws_stream, bcast_tx).await\n" +" });\n" +" }\n" +"}\n" +"```" +msgstr "" + +#: src/exercises/concurrency/chat-app.md:102 +#: src/exercises/concurrency/solutions-afternoon.md:202 +#, fuzzy +msgid "`src/bin/client.rs`:" +msgstr "`src/main.rs`:" + +#: src/exercises/concurrency/chat-app.md:104 +#, fuzzy +msgid "" +msgstr "" + +#: src/exercises/concurrency/chat-app.md:106 +msgid "" +"```rust,compile_fail\n" +"use futures_util::SinkExt;\n" +"use http::Uri;\n" +"use tokio::io::{AsyncBufReadExt, BufReader};\n" +"use tokio_websockets::{ClientBuilder, Message};\n" +"\n" +"#[tokio::main]\n" +"async fn main() -> Result<(), tokio_websockets::Error> {\n" +" let mut ws_stream = ClientBuilder::from_uri(Uri::" +"from_static(\"ws://127.0.0.1:2000\"))\n" +" .connect()\n" +" .await?;\n" +"\n" +" let stdin = tokio::io::stdin();\n" +" let mut stdin = BufReader::new(stdin);\n" +"\n" +"\n" +" // TODO: For a hint, see the description of the task below.\n" +"\n" +"}\n" +"```" +msgstr "" + +#: src/exercises/concurrency/chat-app.md:127 +#, fuzzy +msgid "## Running the binaries" +msgstr "Afvikling af kurset" + +#: src/exercises/concurrency/chat-app.md:128 +msgid "Run the server with:" +msgstr "" + +#: src/exercises/concurrency/chat-app.md:130 +msgid "" +"```shell\n" +"$ cargo run --bin server\n" +"```" +msgstr "" + +#: src/exercises/concurrency/chat-app.md:134 +msgid "and the client with:" +msgstr "" + +#: src/exercises/concurrency/chat-app.md:136 +msgid "" +"```shell\n" +"$ cargo run --bin client\n" +"```" +msgstr "" + +#: src/exercises/concurrency/chat-app.md:142 +msgid "" +"* Implement the `handle_connection` function in `src/bin/server.rs`.\n" +" * Hint: Use `tokio::select!` for concurrently performing two tasks in a\n" +" continuous loop. One task receives messages from the client and " +"broadcasts\n" +" them. The other sends messages received by the server to the client.\n" +"* Complete the main function in `src/bin/client.rs`.\n" +" * Hint: As before, use `tokio::select!` in a continuous loop for " +"concurrently\n" +" performing two tasks: (1) reading user messages from standard input and\n" +" sending them to the server, and (2) receiving messages from the server, " +"and\n" +" displaying them for the user.\n" +"* Optional: Once you are done, change the code to broadcast messages to all\n" +" clients, but the sender of the message." msgstr "" #: src/thanks.md:1 @@ -17675,7 +17503,8 @@ msgstr "" " // ANCHOR_END: luhn\n" " let mut digits_seen = 0;\n" " let mut sum = 0;\n" -" for (i, ch) in cc_number.chars().rev().filter(|&ch| ch != ' ').enumerate() {\n" +" for (i, ch) in cc_number.chars().rev().filter(|&ch| ch != ' ')." +"enumerate() {\n" " match ch.to_digit(10) {\n" " Some(d) => {\n" " sum += if i % 2 == 1 {\n" @@ -17875,12 +17704,15 @@ msgstr "" "#[test]\n" "fn test_matches_without_wildcard() {\n" " assert!(prefix_matches(\"/v1/publishers\", \"/v1/publishers\"));\n" -" assert!(prefix_matches(\"/v1/publishers\", \"/v1/publishers/abc-123\"));\n" -" assert!(prefix_matches(\"/v1/publishers\", \"/v1/publishers/abc/books\"));\n" +" assert!(prefix_matches(\"/v1/publishers\", \"/v1/publishers/" +"abc-123\"));\n" +" assert!(prefix_matches(\"/v1/publishers\", \"/v1/publishers/abc/" +"books\"));\n" "\n" " assert!(!prefix_matches(\"/v1/publishers\", \"/v1\"));\n" " assert!(!prefix_matches(\"/v1/publishers\", \"/v1/publishersBooks\"));\n" -" assert!(!prefix_matches(\"/v1/publishers\", \"/v1/parent/publishers\"));\n" +" assert!(!prefix_matches(\"/v1/publishers\", \"/v1/parent/" +"publishers\"));\n" "}\n" "\n" "#[test]\n" @@ -17898,7 +17730,8 @@ msgstr "" " \"/v1/publishers/foo/books/book1\"\n" " ));\n" "\n" -" assert!(!prefix_matches(\"/v1/publishers/*/books\", \"/v1/publishers\"));\n" +" assert!(!prefix_matches(\"/v1/publishers/*/books\", \"/v1/" +"publishers\"));\n" " assert!(!prefix_matches(\n" " \"/v1/publishers/*/books\",\n" " \"/v1/publishers/foo/booksByAuthor\"\n" @@ -18998,6 +18831,186 @@ msgid "" "```" msgstr "" +#: src/exercises/concurrency/solutions-afternoon.md:113 +msgid "## Broadcast Chat Application" +msgstr "" + +#: src/exercises/concurrency/solutions-afternoon.md:115 +#, fuzzy +msgid "([back to exercise](chat-app.md))" +msgstr "([tilbage til øvelsen](rtc.md))" + +#: src/exercises/concurrency/solutions-afternoon.md:119 +msgid "" +"```rust,compile_fail\n" +"// Copyright 2023 Google LLC\n" +"//\n" +"// Licensed under the Apache License, Version 2.0 (the \"License\");\n" +"// you may not use this file except in compliance with the License.\n" +"// You may obtain a copy of the License at\n" +"//\n" +"// http://www.apache.org/licenses/LICENSE-2.0\n" +"//\n" +"// Unless required by applicable law or agreed to in writing, software\n" +"// distributed under the License is distributed on an \"AS IS\" BASIS,\n" +"// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n" +"// See the License for the specific language governing permissions and\n" +"// limitations under the License.\n" +"\n" +"// ANCHOR: setup\n" +"use futures_util::sink::SinkExt;\n" +"use std::error::Error;\n" +"use std::net::SocketAddr;\n" +"use tokio::net::{TcpListener, TcpStream};\n" +"use tokio::sync::broadcast::{channel, Sender};\n" +"use tokio_websockets::{Message, ServerBuilder, WebsocketStream};\n" +"// ANCHOR_END: setup\n" +"\n" +"// ANCHOR: handle_connection\n" +"async fn handle_connection(\n" +" addr: SocketAddr,\n" +" mut ws_stream: WebsocketStream,\n" +" bcast_tx: Sender,\n" +") -> Result<(), Box> {\n" +" // ANCHOR_END: handle_connection\n" +"\n" +" ws_stream\n" +" .send(Message::text(\"Welcome to chat! Type a message\".into()))\n" +" .await?;\n" +" let mut bcast_rx = bcast_tx.subscribe();\n" +"\n" +" // A continuous loop for concurrently performing two tasks: (1) " +"receiving\n" +" // messages from `ws_stream` and broadcasting them, and (2) receiving\n" +" // messages on `bcast_rx` and sending them to the client.\n" +" loop {\n" +" tokio::select! {\n" +" incoming = ws_stream.next() => {\n" +" match incoming {\n" +" Some(Ok(msg)) => {\n" +" let msg = msg.as_text()?;\n" +" println!(\"From client {addr:?} {msg:?}\");\n" +" bcast_tx.send(msg.into())?;\n" +" }\n" +" Some(Err(err)) => return Err(err.into()),\n" +" None => return Ok(()),\n" +" }\n" +" }\n" +" msg = bcast_rx.recv() => {\n" +" ws_stream.send(Message::text(msg?)).await?;\n" +" }\n" +" }\n" +" }\n" +" // ANCHOR: main\n" +"}\n" +"\n" +"#[tokio::main]\n" +"async fn main() -> Result<(), Box> {\n" +" let (bcast_tx, _) = channel(16);\n" +"\n" +" let listener = TcpListener::bind(\"127.0.0.1:2000\").await?;\n" +" println!(\"listening on port 2000\");\n" +"\n" +" loop {\n" +" let (socket, addr) = listener.accept().await?;\n" +" println!(\"New connection from {addr:?}\");\n" +" let bcast_tx = bcast_tx.clone();\n" +" tokio::spawn(async move {\n" +" // Wrap the raw TCP stream into a websocket.\n" +" let ws_stream = ServerBuilder::new().accept(socket).await?;\n" +"\n" +" handle_connection(addr, ws_stream, bcast_tx).await\n" +" });\n" +" }\n" +"}\n" +"// ANCHOR_END: main\n" +"```" +msgstr "" + +#: src/exercises/concurrency/solutions-afternoon.md:204 +msgid "" +"```rust,compile_fail\n" +"// Copyright 2023 Google LLC\n" +"//\n" +"// Licensed under the Apache License, Version 2.0 (the \"License\");\n" +"// you may not use this file except in compliance with the License.\n" +"// You may obtain a copy of the License at\n" +"//\n" +"// http://www.apache.org/licenses/LICENSE-2.0\n" +"//\n" +"// Unless required by applicable law or agreed to in writing, software\n" +"// distributed under the License is distributed on an \"AS IS\" BASIS,\n" +"// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n" +"// See the License for the specific language governing permissions and\n" +"// limitations under the License.\n" +"\n" +"// ANCHOR: setup\n" +"use futures_util::SinkExt;\n" +"use http::Uri;\n" +"use tokio::io::{AsyncBufReadExt, BufReader};\n" +"use tokio_websockets::{ClientBuilder, Message};\n" +"\n" +"#[tokio::main]\n" +"async fn main() -> Result<(), tokio_websockets::Error> {\n" +" let mut ws_stream = ClientBuilder::from_uri(Uri::" +"from_static(\"ws://127.0.0.1:2000\"))\n" +" .connect()\n" +" .await?;\n" +"\n" +" let stdin = tokio::io::stdin();\n" +" let mut stdin = BufReader::new(stdin);\n" +"\n" +" // ANCHOR_END: setup\n" +" // Continuous loop for concurrently sending and receiving messages.\n" +" loop {\n" +" let mut line = String::new();\n" +" tokio::select! {\n" +" incoming = ws_stream.next() => {\n" +" match incoming {\n" +" Some(Ok(msg)) => println!(\"From server: {}\", msg." +"as_text()?),\n" +" Some(Err(err)) => return Err(err.into()),\n" +" None => return Ok(()),\n" +" }\n" +" }\n" +" res = stdin.read_line(&mut line) => {\n" +" match res {\n" +" Ok(0) => return Ok(()),\n" +" Ok(_) => ws_stream.send(Message::text(line.trim_end()." +"to_string())).await?,\n" +" Err(err) => return Err(err.into()),\n" +" }\n" +" }\n" +"\n" +" }\n" +" }\n" +"}\n" +"```" +msgstr "" + +#~ msgid "" +#~ "
\n" +#~ "Key points:" +#~ msgstr "" +#~ "
\n" +#~ "Nøglepunkter:" + +#~ msgid "" +#~ "
\n" +#~ "Key Points: " +#~ msgstr "" +#~ "
\n" +#~ "Nøglepunkter: " + +#~ msgid "## Getting Started" +#~ msgstr "## Kom godt igang" + +#~ msgid "" +#~ msgstr "" + +#~ msgid "## Exercises" +#~ msgstr "## Øvelser" + #~ msgid "On Day 4, we will cover Android-specific things such as:" #~ msgstr "På dag 4 vil vi dække Android-specifikke ting såsom:"