You've already forked comprehensive-rust
							
							
				mirror of
				https://github.com/google/comprehensive-rust.git
				synced 2025-10-31 08:37:45 +02:00 
			
		
		
		
	Add the description of the chat-app exercise (#641)
* Adds a description of the async chat exercise * Simplifies the use of Error in chat-async * Links the solution to the async chat exercise * Removes the elevator exercise
This commit is contained in:
		
							
								
								
									
										16
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										16
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							| @@ -187,7 +187,6 @@ version = "0.1.0" | ||||
| dependencies = [ | ||||
|  "futures-util", | ||||
|  "http", | ||||
|  "thiserror", | ||||
|  "tokio", | ||||
|  "tokio-websockets", | ||||
| ] | ||||
| @@ -1996,6 +1995,15 @@ version = "1.1.0" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3" | ||||
|  | ||||
| [[package]] | ||||
| name = "signal-hook-registry" | ||||
| version = "1.4.1" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" | ||||
| dependencies = [ | ||||
|  "libc", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "siphasher" | ||||
| version = "0.3.10" | ||||
| @@ -2185,7 +2193,9 @@ dependencies = [ | ||||
|  "libc", | ||||
|  "mio", | ||||
|  "num_cpus", | ||||
|  "parking_lot", | ||||
|  "pin-project-lite", | ||||
|  "signal-hook-registry", | ||||
|  "socket2", | ||||
|  "tokio-macros", | ||||
|  "windows-sys 0.48.0", | ||||
| @@ -2251,9 +2261,9 @@ dependencies = [ | ||||
|  | ||||
| [[package]] | ||||
| name = "tokio-websockets" | ||||
| version = "0.3.0" | ||||
| version = "0.3.2" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "d009a9a2a71bd44791363f42ba2dd7d47bf49aa61f7c43eabcd29a285e75a865" | ||||
| checksum = "8d1df20f31f428a351ec353699a82aad49360090e8f6320ac0dacf5def4f1573" | ||||
| dependencies = [ | ||||
|  "base64 0.21.0", | ||||
|  "bytes", | ||||
|   | ||||
| @@ -52,6 +52,7 @@ editable = true | ||||
| "exercises/day-4/link-checker.html" = "../concurrency/link-checker.html" | ||||
| "exercises/day-4/morning.html" = "../concurrency/morning.html" | ||||
| "exercises/day-4/solutions-morning.html" = "../concurrency/solutions-morning.html" | ||||
| "exercises/concurrency/elevator.html" = "chat-app.html" | ||||
| "generics/closures.html" = "../traits/closures.html" | ||||
| "generics/impl-trait.html" = "../traits/impl-trait.html" | ||||
| "generics/trait-bounds.html" = "../traits/trait-bounds.html" | ||||
|   | ||||
| @@ -292,7 +292,7 @@ | ||||
|   - [Async Traits](async/pitfalls/async-traits.md) | ||||
| - [Exercises](exercises/concurrency/afternoon.md) | ||||
|   - [Dining Philosophers](exercises/concurrency/dining-philosophers-async.md) | ||||
|   - [Elevator Operations](exercises/concurrency/elevator.md) | ||||
|   - [Broadcast Chat Application](exercises/concurrency/chat-app.md) | ||||
|  | ||||
|  | ||||
| # Final Words | ||||
|   | ||||
| @@ -5,8 +5,8 @@ To practice your Async Rust skills, we have again two exercises for you: | ||||
| * Dining philosophers: we already saw this problem in the morning. This time | ||||
|   you are going to implement it with Async Rust. | ||||
|  | ||||
| * The Elevator Problem: this is a larger project that allows you experiment | ||||
|   with more advanced Async Rust features and some of its pitfalls! | ||||
| * A Broadcast Chat Application: this is a larger project that allows you | ||||
|   experiment with more advanced Async Rust features. | ||||
|  | ||||
| <details> | ||||
|  | ||||
|   | ||||
							
								
								
									
										109
									
								
								src/exercises/concurrency/chat-app.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										109
									
								
								src/exercises/concurrency/chat-app.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,109 @@ | ||||
| # Broadcast Chat Application | ||||
|  | ||||
| In this exercise, we want to use our new knowledge to implement a broadcast | ||||
| chat application. We have a chat server that the clients connect to and publish | ||||
| their messages. The client reads user messages from the standard input, and | ||||
| sends them to the server. The chat server broadcasts each message that it | ||||
| receives to all the clients. | ||||
|  | ||||
| For this, we use [a broadcast channel][1] on the server, and | ||||
| [`tokio_websockets`][2] for the communication between the client and the | ||||
| server. | ||||
|  | ||||
| Create a new Cargo project and add the following dependencies: | ||||
|  | ||||
| `Cargo.toml`: | ||||
|  | ||||
| <!-- File Cargo.toml --> | ||||
|  | ||||
| ```toml | ||||
| {{#include chat-async/Cargo.toml}} | ||||
| ``` | ||||
|  | ||||
| ## The required APIs | ||||
| You are going to need the following functions from `tokio` and | ||||
| [`tokio_websockets`][2]. Spend a few minutes to familiarize yourself with the | ||||
| API.  | ||||
|  | ||||
| - [WebsocketStream::next()][3]: for asynchronously reading messages from a | ||||
|   Websocket Stream. | ||||
| - [SinkExt::send()][4] implemented by `WebsocketStream`: for asynchronously | ||||
|   sending messages on a Websocket Stream. | ||||
| - [BufReader::read_line()][5]: for asynchronously reading user messages | ||||
|   from the standard input. | ||||
| - [Sender::subscribe()][6]: for subscribing to a broadcast channel. | ||||
|  | ||||
|  | ||||
| ## Two binaries | ||||
|  | ||||
| Normally in a Cargo project, you can have only one binary, and one | ||||
| `src/main.rs` file. In this project, we need two binaries. One for the client, | ||||
| and one for the server. You could potentially make them two separate Cargo | ||||
| projects, but we are going to put them in a single Cargo project with two | ||||
| binaries. For this to work, the client and the server code should go under | ||||
| `src/bin` (see the [documentation][7]).  | ||||
|  | ||||
| Copy the following server and client code into `src/bin/server.rs` and | ||||
| `src/bin/client.rs`, respectively. Your task is to complete these files as | ||||
| described below.  | ||||
|  | ||||
| `src/bin/server.rs`: | ||||
|  | ||||
| <!-- File src/bin/server.rs --> | ||||
|  | ||||
| ```rust,compile_fail | ||||
| {{#include chat-async/src/bin/server.rs:setup}} | ||||
|  | ||||
| {{#include chat-async/src/bin/server.rs:handle_connection}} | ||||
|  | ||||
|     // TODO: For a hint, see the description of the task below. | ||||
|  | ||||
| {{#include chat-async/src/bin/server.rs:main}} | ||||
| ``` | ||||
|  | ||||
| `src/bin/client.rs`: | ||||
|  | ||||
| <!-- File src/bin/client.rs --> | ||||
|  | ||||
| ```rust,compile_fail | ||||
| {{#include chat-async/src/bin/client.rs:setup}} | ||||
|  | ||||
|     // TODO: For a hint, see the description of the task below. | ||||
|  | ||||
| } | ||||
| ``` | ||||
|  | ||||
| ## Running the binaries | ||||
| Run the server with: | ||||
|  | ||||
| ```shell | ||||
| $ cargo run --bin server | ||||
| ``` | ||||
|  | ||||
| and the client with: | ||||
|  | ||||
| ```shell | ||||
| $ cargo run --bin client | ||||
| ``` | ||||
|  | ||||
| ## Tasks | ||||
|  | ||||
| * Implement the `handle_connection` function in `src/bin/server.rs`. | ||||
|   * Hint: Use `tokio::select!` for concurrently performing two tasks in a | ||||
|     continuous loop. One task receives messages from the client and broadcasts | ||||
|     them. The other sends messages received by the server to the client. | ||||
| * Complete the main function in `src/bin/client.rs`. | ||||
|   * Hint: As before, use `tokio::select!` in a continuous loop for concurrently | ||||
|     performing two tasks: (1) reading user messages from standard input and | ||||
|     sending them to the server, and (2) receiving messages from the server, and | ||||
|     displaying them for the user. | ||||
| * Optional: Once you are done, change the code to broadcast messages to all | ||||
|   clients, but the sender of the message. | ||||
|  | ||||
| [1]: https://docs.rs/tokio/latest/tokio/sync/broadcast/fn.channel.html | ||||
| [2]: https://docs.rs/tokio-websockets/0.3.2/tokio_websockets/ | ||||
| [3]: https://docs.rs/tokio-websockets/0.3.2/tokio_websockets/proto/struct.WebsocketStream.html#method.next | ||||
| [4]: https://docs.rs/futures-util/0.3.28/futures_util/sink/trait.SinkExt.html#method.send | ||||
| [5]: https://docs.rs/tokio/latest/tokio/io/trait.AsyncBufReadExt.html#method.read_line | ||||
| [6]: https://docs.rs/tokio/latest/tokio/sync/broadcast/struct.Sender.html#method.subscribe | ||||
| [7]: https://doc.rust-lang.org/cargo/reference/cargo-targets.html#binaries | ||||
| @@ -6,6 +6,5 @@ edition = "2021" | ||||
| [dependencies] | ||||
| futures-util = "0.3.28" | ||||
| http = "0.2.9" | ||||
| thiserror = "1.0.40" | ||||
| tokio = { version = "1.28.1", features = ["net", "macros", "time", "rt", "rt-multi-thread", "io-std", "io-util"] } | ||||
| tokio-websockets = "0.3.0" | ||||
| tokio = { version = "1.28.1", features = ["full"] } | ||||
| tokio-websockets = "0.3.2" | ||||
|   | ||||
| @@ -1,3 +1,18 @@ | ||||
| // Copyright 2023 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 futures_util::SinkExt; | ||||
| use http::Uri; | ||||
| use tokio::io::{AsyncBufReadExt, BufReader}; | ||||
| @@ -12,6 +27,8 @@ async fn main() -> Result<(), tokio_websockets::Error> { | ||||
|     let stdin = tokio::io::stdin(); | ||||
|     let mut stdin = BufReader::new(stdin); | ||||
|  | ||||
|     // ANCHOR_END: setup | ||||
|     // Continuous loop for concurrently sending and receiving messages. | ||||
|     loop { | ||||
|         let mut line = String::new(); | ||||
|         tokio::select! { | ||||
|   | ||||
| @@ -1,46 +1,42 @@ | ||||
| // Copyright 2023 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 futures_util::sink::SinkExt; | ||||
| use std::error::Error; | ||||
| use std::net::SocketAddr; | ||||
| use thiserror::Error; | ||||
| use tokio::net::{TcpListener, TcpStream}; | ||||
| use tokio::sync::broadcast::error::{RecvError, SendError}; | ||||
| use tokio::sync::broadcast::{channel, Sender}; | ||||
| use tokio_websockets::{Message, ServerBuilder, WebsocketStream}; | ||||
| // ANCHOR_END: setup | ||||
|  | ||||
| #[derive(Error, Debug)] | ||||
| enum ServerError { | ||||
|     #[error("websocket error: {0}")] | ||||
|     Websocket(String), | ||||
|     #[error("io error: {0}")] | ||||
|     IO(#[from] std::io::Error), | ||||
|     #[error("broadcast channel SendError: {0}")] | ||||
|     SendError(#[from] SendError<String>), | ||||
|     #[error("broadcast channel RecvError: {0}")] | ||||
|     RecvError(#[from] RecvError), | ||||
| } | ||||
|  | ||||
| // tokio_websockets Error types do not implement std::error::Error, so we make do by just capturing | ||||
| // the debug format for the error. | ||||
| impl From<tokio_websockets::Error> for ServerError { | ||||
|     fn from(err: tokio_websockets::Error) -> Self { | ||||
|         ServerError::Websocket(format!("{:?}", err)) | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl From<tokio_websockets::proto::ProtocolError> for ServerError { | ||||
|     fn from(err: tokio_websockets::proto::ProtocolError) -> Self { | ||||
|         ServerError::Websocket(format!("{:?}", err)) | ||||
|     } | ||||
| } | ||||
|  | ||||
| // ANCHOR: handle_connection | ||||
| async fn handle_connection( | ||||
|     addr: SocketAddr, | ||||
|     mut ws_stream: WebsocketStream<TcpStream>, | ||||
|     bcast_tx: Sender<String>, | ||||
| ) -> Result<(), ServerError> { | ||||
| ) -> Result<(), Box<dyn Error + Send + Sync>> { | ||||
|     // ANCHOR_END: handle_connection | ||||
|  | ||||
|     ws_stream | ||||
|         .send(Message::text("Welcome to chat! Type a message".into())) | ||||
|         .await?; | ||||
|     let mut bcast_rx = bcast_tx.subscribe(); | ||||
|  | ||||
|     // A continuous loop for concurrently performing two tasks: (1) receiving | ||||
|     // messages from `ws_stream` and broadcasting them, and (2) receiving | ||||
|     // messages on `bcast_rx` and sending them to the client. | ||||
|     loop { | ||||
|         tokio::select! { | ||||
|             incoming = ws_stream.next() => { | ||||
| @@ -59,10 +55,11 @@ async fn handle_connection( | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|     // ANCHOR: main | ||||
| } | ||||
|  | ||||
| #[tokio::main] | ||||
| async fn main() -> Result<(), ServerError> { | ||||
| async fn main() -> Result<(), Box<dyn Error + Send + Sync>> { | ||||
|     let (bcast_tx, _) = channel(16); | ||||
|  | ||||
|     let listener = TcpListener::bind("127.0.0.1:2000").await?; | ||||
| @@ -80,3 +77,4 @@ async fn main() -> Result<(), ServerError> { | ||||
|         }); | ||||
|     } | ||||
| } | ||||
| // ANCHOR_END: main | ||||
|   | ||||
| @@ -1,91 +0,0 @@ | ||||
| # Elevator Operation | ||||
|  | ||||
| Elevators seem simple. You press a button, doors open, you wait, and you're at | ||||
| the floor you requested. But implementing an elevator controller is surprisingly | ||||
| difficult! This exercise involves building a simple elevator control that | ||||
| operates in a simple simulator. | ||||
|  | ||||
| The overall design of this elevator uses the actor pattern: you will implement a | ||||
| controller task that communicates with other components of the elevator system | ||||
| by sending and receiving messages. | ||||
|  | ||||
| ## Getting Started | ||||
|  | ||||
| Download the [exercise template](../../comprehensive-rust-exercises.zip) and look in the `elevator` | ||||
| directory for the following files. | ||||
|  | ||||
| `src/main.rs`: | ||||
|  | ||||
| <!-- File src/main.rs --> | ||||
|  | ||||
| ```rust,compile_fail | ||||
| {{#include elevator/src/main.rs}} | ||||
| ``` | ||||
|  | ||||
| `src/building.rs`: | ||||
|  | ||||
| <!-- File src/building.rs --> | ||||
|  | ||||
| ```rust,compile_fail | ||||
| {{#include elevator/src/building.rs}} | ||||
| ``` | ||||
|  | ||||
| `src/driver.rs`: | ||||
|  | ||||
| <!-- File src/driver.rs --> | ||||
|  | ||||
| ```rust,compile_fail | ||||
| {{#include elevator/src/driver.rs}} | ||||
| ``` | ||||
|  | ||||
| `src/controller.rs`: | ||||
|  | ||||
| <!-- File src/controller.rs --> | ||||
|  | ||||
| ```rust,compile_fail | ||||
| {{#include elevator/src/controller.rs}} | ||||
| ``` | ||||
|  | ||||
| `Cargo.toml` (you shouldn't need to change this): | ||||
|  | ||||
| <!-- File Cargo.toml --> | ||||
|  | ||||
| ```toml | ||||
| {{#include elevator/Cargo.toml}} | ||||
| ``` | ||||
|  | ||||
| Use `cargo run` to run the elevator simulation. | ||||
|  | ||||
| ## Exercises | ||||
|  | ||||
| Begin by implementing a controller that can transport the passengers provided by | ||||
| the simple driver. There is only one elevator, and passengers always go from | ||||
| floor 0 to floor 2, one-by-one. | ||||
|  | ||||
| Once you have this done, make the problem more complex. Suggested tasks: | ||||
|  | ||||
|  * Make the driver more complex, with passengers arriving at random floors with | ||||
|    random destinations at random times. | ||||
|  | ||||
|  * Create a building with more than one elevator, and adjust the controller to | ||||
|    handle this efficiently. | ||||
|  | ||||
|  * Add additional events and metadata to analyze your controller's efficiency. | ||||
|    What is the distribution of wait time for passengers? Is the result fair? | ||||
|  | ||||
|  * Modify the building to support a maximum passenger capacity for each | ||||
|    elevator, and modify the controller to take this information into account. | ||||
|  | ||||
|  * Update the driver to simulate business traffic, with lots of passengers going | ||||
|    up from the ground floor at the same time, and those passengers returning to | ||||
|    the ground floor some time later. Can your controller adjust to these | ||||
|    circumstances? | ||||
|  | ||||
|  * Modify the building to support "destination dispatch", where passengers | ||||
|    signal their destination floor in the elevator lobby, before boarding the | ||||
|    elevator. | ||||
|  | ||||
|  * If you are taking the course with other students, trade controllers or | ||||
|    drivers with another student to see how robust your design is. | ||||
|  | ||||
|  * Build a textual or graphical display of the elevators as they run. | ||||
							
								
								
									
										318
									
								
								src/exercises/concurrency/elevator/Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										318
									
								
								src/exercises/concurrency/elevator/Cargo.lock
									
									
									
										generated
									
									
									
								
							| @@ -1,318 +0,0 @@ | ||||
| # This file is automatically @generated by Cargo. | ||||
| # It is not intended for manual editing. | ||||
| version = 3 | ||||
|  | ||||
| [[package]] | ||||
| name = "autocfg" | ||||
| version = "1.1.0" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" | ||||
|  | ||||
| [[package]] | ||||
| name = "bitflags" | ||||
| version = "1.3.2" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" | ||||
|  | ||||
| [[package]] | ||||
| name = "bytes" | ||||
| version = "1.4.0" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" | ||||
|  | ||||
| [[package]] | ||||
| name = "cfg-if" | ||||
| version = "1.0.0" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" | ||||
|  | ||||
| [[package]] | ||||
| name = "elevator" | ||||
| version = "0.1.0" | ||||
| dependencies = [ | ||||
|  "tokio", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "hermit-abi" | ||||
| version = "0.2.6" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7" | ||||
| dependencies = [ | ||||
|  "libc", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "libc" | ||||
| version = "0.2.141" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "3304a64d199bb964be99741b7a14d26972741915b3649639149b2479bb46f4b5" | ||||
|  | ||||
| [[package]] | ||||
| name = "lock_api" | ||||
| version = "0.4.9" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df" | ||||
| dependencies = [ | ||||
|  "autocfg", | ||||
|  "scopeguard", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "log" | ||||
| version = "0.4.17" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" | ||||
| dependencies = [ | ||||
|  "cfg-if", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "mio" | ||||
| version = "0.8.6" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "5b9d9a46eff5b4ff64b45a9e316a6d1e0bc719ef429cbec4dc630684212bfdf9" | ||||
| dependencies = [ | ||||
|  "libc", | ||||
|  "log", | ||||
|  "wasi", | ||||
|  "windows-sys", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "num_cpus" | ||||
| version = "1.15.0" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b" | ||||
| dependencies = [ | ||||
|  "hermit-abi", | ||||
|  "libc", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "parking_lot" | ||||
| version = "0.12.1" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" | ||||
| dependencies = [ | ||||
|  "lock_api", | ||||
|  "parking_lot_core", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "parking_lot_core" | ||||
| version = "0.9.7" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "9069cbb9f99e3a5083476ccb29ceb1de18b9118cafa53e90c9551235de2b9521" | ||||
| dependencies = [ | ||||
|  "cfg-if", | ||||
|  "libc", | ||||
|  "redox_syscall", | ||||
|  "smallvec", | ||||
|  "windows-sys", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "pin-project-lite" | ||||
| version = "0.2.9" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" | ||||
|  | ||||
| [[package]] | ||||
| name = "proc-macro2" | ||||
| version = "1.0.56" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "2b63bdb0cd06f1f4dedf69b254734f9b45af66e4a031e42a7480257d9898b435" | ||||
| dependencies = [ | ||||
|  "unicode-ident", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "quote" | ||||
| version = "1.0.26" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "4424af4bf778aae2051a77b60283332f386554255d722233d09fbfc7e30da2fc" | ||||
| dependencies = [ | ||||
|  "proc-macro2", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "redox_syscall" | ||||
| version = "0.2.16" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" | ||||
| dependencies = [ | ||||
|  "bitflags", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "scopeguard" | ||||
| version = "1.1.0" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" | ||||
|  | ||||
| [[package]] | ||||
| name = "signal-hook-registry" | ||||
| version = "1.4.1" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" | ||||
| dependencies = [ | ||||
|  "libc", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "smallvec" | ||||
| version = "1.10.0" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" | ||||
|  | ||||
| [[package]] | ||||
| name = "socket2" | ||||
| version = "0.4.9" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662" | ||||
| dependencies = [ | ||||
|  "libc", | ||||
|  "winapi", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "syn" | ||||
| version = "2.0.13" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "4c9da457c5285ac1f936ebd076af6dac17a61cfe7826f2076b4d015cf47bc8ec" | ||||
| dependencies = [ | ||||
|  "proc-macro2", | ||||
|  "quote", | ||||
|  "unicode-ident", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "tokio" | ||||
| version = "1.27.0" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "d0de47a4eecbe11f498978a9b29d792f0d2692d1dd003650c24c76510e3bc001" | ||||
| dependencies = [ | ||||
|  "autocfg", | ||||
|  "bytes", | ||||
|  "libc", | ||||
|  "mio", | ||||
|  "num_cpus", | ||||
|  "parking_lot", | ||||
|  "pin-project-lite", | ||||
|  "signal-hook-registry", | ||||
|  "socket2", | ||||
|  "tokio-macros", | ||||
|  "windows-sys", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "tokio-macros" | ||||
| version = "2.0.0" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "61a573bdc87985e9d6ddeed1b3d864e8a302c847e40d647746df2f1de209d1ce" | ||||
| dependencies = [ | ||||
|  "proc-macro2", | ||||
|  "quote", | ||||
|  "syn", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "unicode-ident" | ||||
| version = "1.0.8" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" | ||||
|  | ||||
| [[package]] | ||||
| name = "wasi" | ||||
| version = "0.11.0+wasi-snapshot-preview1" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" | ||||
|  | ||||
| [[package]] | ||||
| name = "winapi" | ||||
| version = "0.3.9" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" | ||||
| dependencies = [ | ||||
|  "winapi-i686-pc-windows-gnu", | ||||
|  "winapi-x86_64-pc-windows-gnu", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "winapi-i686-pc-windows-gnu" | ||||
| version = "0.4.0" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" | ||||
|  | ||||
| [[package]] | ||||
| name = "winapi-x86_64-pc-windows-gnu" | ||||
| version = "0.4.0" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" | ||||
|  | ||||
| [[package]] | ||||
| name = "windows-sys" | ||||
| version = "0.45.0" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" | ||||
| dependencies = [ | ||||
|  "windows-targets", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "windows-targets" | ||||
| version = "0.42.2" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" | ||||
| dependencies = [ | ||||
|  "windows_aarch64_gnullvm", | ||||
|  "windows_aarch64_msvc", | ||||
|  "windows_i686_gnu", | ||||
|  "windows_i686_msvc", | ||||
|  "windows_x86_64_gnu", | ||||
|  "windows_x86_64_gnullvm", | ||||
|  "windows_x86_64_msvc", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "windows_aarch64_gnullvm" | ||||
| version = "0.42.2" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" | ||||
|  | ||||
| [[package]] | ||||
| name = "windows_aarch64_msvc" | ||||
| version = "0.42.2" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" | ||||
|  | ||||
| [[package]] | ||||
| name = "windows_i686_gnu" | ||||
| version = "0.42.2" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" | ||||
|  | ||||
| [[package]] | ||||
| name = "windows_i686_msvc" | ||||
| version = "0.42.2" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" | ||||
|  | ||||
| [[package]] | ||||
| name = "windows_x86_64_gnu" | ||||
| version = "0.42.2" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" | ||||
|  | ||||
| [[package]] | ||||
| name = "windows_x86_64_gnullvm" | ||||
| version = "0.42.2" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" | ||||
|  | ||||
| [[package]] | ||||
| name = "windows_x86_64_msvc" | ||||
| version = "0.42.2" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" | ||||
| @@ -1,9 +0,0 @@ | ||||
| [workspace] | ||||
|  | ||||
| [package] | ||||
| name = "elevator" | ||||
| version = "0.1.0" | ||||
| edition = "2021" | ||||
|  | ||||
| [dependencies] | ||||
| tokio = { version = "1.26.0", features = ["full"] } | ||||
| @@ -1,234 +0,0 @@ | ||||
| //! The building simulates floors and elevators. | ||||
|  | ||||
| use tokio::sync::{broadcast, mpsc}; | ||||
| use tokio::task; | ||||
| use tokio::time; | ||||
|  | ||||
| #[derive(Debug, Clone)] | ||||
| pub enum Direction { | ||||
|     Up, | ||||
|     Down, | ||||
| } | ||||
|  | ||||
| /// A passenger is a person with a destination floor in mind. | ||||
| #[derive(Debug)] | ||||
| struct Passenger { | ||||
|     destination: FloorId, | ||||
| } | ||||
|  | ||||
| /// FloorId identifies a floor. These are zero-based integers. | ||||
| pub type FloorId = usize; | ||||
|  | ||||
| /// Floor represents the current status of a floor in the building. | ||||
| #[derive(Default, Debug)] | ||||
| struct Floor { | ||||
|     passengers: Vec<Passenger>, | ||||
| } | ||||
|  | ||||
| /// ElevatorId identifies an elevator in the building. These are zero-based integers. | ||||
| pub type ElevatorId = usize; | ||||
|  | ||||
| /// Elevator represents the current status of an elevator in the building. | ||||
| #[derive(Default, Debug)] | ||||
| struct Elevator { | ||||
|     /// Floor the elevator is currently on. In the simulation the elevator | ||||
|     /// transports instantaneously from one floor to the next in a single | ||||
|     /// simulation tick. | ||||
|     position: FloorId, | ||||
|     /// Destination floor for the elevator, if any. This can change at any time. | ||||
|     destination: Option<FloorId>, | ||||
|     /// Passengers currently on the elevator. | ||||
|     passengers: Vec<Passenger>, | ||||
|     /// True if the elevator is stopped with the doors open. The elevator | ||||
|     /// will not move with the doors open, but they will close at the next | ||||
|     /// tick of the simulation. | ||||
|     doors_open: bool, | ||||
| } | ||||
|  | ||||
| /// A BuildingEvent is an event that occurs in the building. | ||||
| #[derive(Debug, Clone)] | ||||
| pub enum BuildingEvent { | ||||
|     /// A passenger has pressed a floor button in the elevator. | ||||
|     FloorButtonPressed(ElevatorId, FloorId), | ||||
|     /// A passenger on the given floor has pressed the call button. | ||||
|     CallButtonPressed(FloorId, Direction), | ||||
|     /// The elevator has arrived at the given floor. If this is the | ||||
|     /// elevator's destination, then it will stop open its doors. | ||||
|     AtFloor(ElevatorId, FloorId), | ||||
|     /// A passenger has been delivered to their desired floor. | ||||
|     PassengerDelivered(FloorId), | ||||
| } | ||||
|  | ||||
| /// A BuildingCommand tells the building what to do. | ||||
| #[derive(Debug)] | ||||
| pub enum BuildingCommand { | ||||
|     /// Set the elevator's destination. The elevator will close its doors | ||||
|     /// if necessary and then begin moving toward this floor. | ||||
|     GoToFloor(ElevatorId, FloorId), | ||||
| } | ||||
|  | ||||
| /// A DriverCommand is a message from the driver to change the state of | ||||
| /// the building. | ||||
| #[derive(Debug)] | ||||
| pub enum DriverCommand { | ||||
|     /// A passenger has arrived and is waiting for an elevator. The passenger will automatically | ||||
|     /// press the relevant call button, board the elevator when it arrives, press their floor | ||||
|     /// button, and depart when the doors open on their destination floor. | ||||
|     PassengerArrived { at: FloorId, destination: FloorId }, | ||||
|  | ||||
|     /// Halt all activity in the building and end the building task. | ||||
|     Halt, | ||||
| } | ||||
|  | ||||
| /// Building manages the current status of the building. | ||||
| #[derive(Debug)] | ||||
| pub struct Building { | ||||
|     floors: Vec<Floor>, | ||||
|     elevators: Vec<Elevator>, | ||||
| } | ||||
|  | ||||
| impl Building { | ||||
|     pub fn new(num_floors: usize, num_elevators: usize) -> Self { | ||||
|         let mut floors = vec![]; | ||||
|         for _ in 0..num_floors { | ||||
|             floors.push(Floor::default()); | ||||
|         } | ||||
|         let mut elevators = vec![]; | ||||
|         for _ in 0..num_elevators { | ||||
|             elevators.push(Elevator::default()); | ||||
|         } | ||||
|         Self { floors, elevators } | ||||
|     } | ||||
|  | ||||
|     /// Start the building. The resulting channels are used to communicate | ||||
|     /// with the building | ||||
|     pub fn start( | ||||
|         self, | ||||
|     ) -> ( | ||||
|         task::JoinHandle<()>, | ||||
|         broadcast::Receiver<BuildingEvent>, | ||||
|         mpsc::Sender<BuildingCommand>, | ||||
|         mpsc::Sender<DriverCommand>, | ||||
|     ) { | ||||
|         let (events_tx, events_rx) = broadcast::channel(10); | ||||
|         let (building_cmd_tx, building_cmd_rx) = mpsc::channel(10); | ||||
|         let (driver_cmd_tx, driver_cmd_rx) = mpsc::channel(10); | ||||
|         let task = tokio::spawn(self.run(events_tx, building_cmd_rx, driver_cmd_rx)); | ||||
|         (task, events_rx, building_cmd_tx, driver_cmd_tx) | ||||
|     } | ||||
|  | ||||
|     async fn run( | ||||
|         mut self, | ||||
|         events_tx: broadcast::Sender<BuildingEvent>, | ||||
|         mut building_cmd_rx: mpsc::Receiver<BuildingCommand>, | ||||
|         mut driver_cmd_rx: mpsc::Receiver<DriverCommand>, | ||||
|     ) { | ||||
|         let mut ticker = time::interval(time::Duration::from_millis(100)); | ||||
|         loop { | ||||
|             tokio::select! { | ||||
|                 Some(BuildingCommand::GoToFloor(el, fl)) = building_cmd_rx.recv() => { | ||||
|                     self.elevators[el].destination = Some(fl); | ||||
|                 } | ||||
|                 Some(cmd) = driver_cmd_rx.recv() => { | ||||
|                     match cmd { | ||||
|                         DriverCommand::PassengerArrived{at, destination} => { | ||||
|                             self.new_passenger(&events_tx, at, destination).await; | ||||
|                         } | ||||
|                         DriverCommand::Halt => return, | ||||
|                     } | ||||
|                 } | ||||
|                 _ = ticker.tick() => self.move_elevators(&events_tx).await | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// Move the elevators toward their destinations. | ||||
|     async fn move_elevators(&mut self, events_tx: &broadcast::Sender<BuildingEvent>) { | ||||
|         for el in 0..self.elevators.len() { | ||||
|             let elevator = &mut self.elevators[el]; | ||||
|  | ||||
|             // If the elevator's doors are open, close them and wait for the next tick. | ||||
|             if elevator.doors_open { | ||||
|                 elevator.doors_open = false; | ||||
|                 continue; | ||||
|             } | ||||
|  | ||||
|             // If the elevator has somewhere to go, move toward it. | ||||
|             if let Some(dest) = elevator.destination { | ||||
|                 if dest > elevator.position { | ||||
|                     elevator.position += 1; | ||||
|                 } | ||||
|                 if dest < elevator.position { | ||||
|                     elevator.position -= 1; | ||||
|                 } | ||||
|                 events_tx | ||||
|                     .send(BuildingEvent::AtFloor(el, elevator.position)) | ||||
|                     .unwrap(); | ||||
|  | ||||
|                 // If the elevator has reached its destination, open | ||||
|                 // the doors and let passengers get on and off. | ||||
|                 if elevator.position == dest { | ||||
|                     elevator.destination = None; | ||||
|                     elevator.doors_open = true; | ||||
|                     self.exchange_passengers(&events_tx, el).await; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// Handle a new passenger arriving at the given floor. | ||||
|     async fn new_passenger( | ||||
|         &mut self, | ||||
|         events_tx: &broadcast::Sender<BuildingEvent>, | ||||
|         at: FloorId, | ||||
|         destination: FloorId, | ||||
|     ) { | ||||
|         println!("Passenger arrived at {} going to {}", at, destination); | ||||
|         if at == destination { | ||||
|             events_tx | ||||
|                 .send(BuildingEvent::PassengerDelivered(destination)) | ||||
|                 .unwrap(); | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         self.floors[at].passengers.push(Passenger { destination }); | ||||
|         let dir = if at < destination { | ||||
|             Direction::Up | ||||
|         } else { | ||||
|             Direction::Down | ||||
|         }; | ||||
|         events_tx | ||||
|             .send(BuildingEvent::CallButtonPressed(at, dir)) | ||||
|             .unwrap(); | ||||
|     } | ||||
|  | ||||
|     /// The doors for the given elevator are open, so take on and discharge passengers. | ||||
|     async fn exchange_passengers( | ||||
|         &mut self, | ||||
|         events_tx: &broadcast::Sender<BuildingEvent>, | ||||
|         el: ElevatorId, | ||||
|     ) { | ||||
|         let elevator = &mut self.elevators[el]; | ||||
|         let fl = elevator.position; | ||||
|  | ||||
|         // Handle passengers leaving the elevator at their floor. | ||||
|         let (this_floor, other_floors): (Vec<Passenger>, Vec<Passenger>) = elevator | ||||
|             .passengers | ||||
|             .drain(..) | ||||
|             .partition(|px| px.destination == fl); | ||||
|         for px in this_floor { | ||||
|             events_tx | ||||
|                 .send(BuildingEvent::PassengerDelivered(px.destination)) | ||||
|                 .unwrap(); | ||||
|         } | ||||
|         elevator.passengers = other_floors; | ||||
|  | ||||
|         // Handle passengers entering the elevator. | ||||
|         for px in self.floors[fl].passengers.drain(..) { | ||||
|             events_tx | ||||
|                 .send(BuildingEvent::FloorButtonPressed(el, px.destination)) | ||||
|                 .unwrap(); | ||||
|             elevator.passengers.push(px); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -1,28 +0,0 @@ | ||||
| //! The controller directs the elevators to operate so that passengers | ||||
| //! get to their destinations. | ||||
|  | ||||
| use crate::building::{BuildingCommand, BuildingEvent}; | ||||
| use tokio::sync::{broadcast, mpsc}; | ||||
|  | ||||
| pub async fn controller( | ||||
|     mut events_rx: broadcast::Receiver<BuildingEvent>, | ||||
|     building_cmd_tx: mpsc::Sender<BuildingCommand>, | ||||
| ) { | ||||
|     while let Ok(evt) = events_rx.recv().await { | ||||
|         match evt { | ||||
|             BuildingEvent::CallButtonPressed(at, _) => { | ||||
|                 building_cmd_tx | ||||
|                     .send(BuildingCommand::GoToFloor(0, at)) | ||||
|                     .await | ||||
|                     .unwrap(); | ||||
|             } | ||||
|             BuildingEvent::FloorButtonPressed(_, destination) => { | ||||
|                 building_cmd_tx | ||||
|                     .send(BuildingCommand::GoToFloor(0, destination)) | ||||
|                     .await | ||||
|                     .unwrap(); | ||||
|             } | ||||
|             _ => {} | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -1,35 +0,0 @@ | ||||
| //! The driver controls when and where passengers arrive. | ||||
|  | ||||
| use crate::building::{Building, BuildingEvent, DriverCommand}; | ||||
| use tokio::sync::{broadcast, mpsc}; | ||||
|  | ||||
| /// Create a new building to be driven by this driver. | ||||
| pub fn make_building() -> Building { | ||||
|     Building::new(3, 1) | ||||
| } | ||||
|  | ||||
| /// Simulate people arriving at the ground floor and going to the first floor, one by one. | ||||
| pub async fn driver( | ||||
|     mut events_rx: broadcast::Receiver<BuildingEvent>, | ||||
|     driver_cmd_tx: mpsc::Sender<DriverCommand>, | ||||
| ) { | ||||
|     for _ in 0..3 { | ||||
|         // A passenger has arrived.. | ||||
|         driver_cmd_tx | ||||
|             .send(DriverCommand::PassengerArrived { | ||||
|                 at: 0, | ||||
|                 destination: 2, | ||||
|             }) | ||||
|             .await | ||||
|             .unwrap(); | ||||
|  | ||||
|         // Wait until they are delivered.. | ||||
|         while let Ok(evt) = events_rx.recv().await { | ||||
|             if let BuildingEvent::PassengerDelivered(_) = evt { | ||||
|                 break; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     driver_cmd_tx.send(DriverCommand::Halt).await.unwrap(); | ||||
| } | ||||
| @@ -1,23 +0,0 @@ | ||||
| use building::BuildingEvent; | ||||
| use tokio::sync::broadcast; | ||||
|  | ||||
| mod building; | ||||
| mod controller; | ||||
| mod driver; | ||||
|  | ||||
| #[tokio::main] | ||||
| async fn main() { | ||||
|     let building = driver::make_building(); | ||||
|     let (building_task, events_rx, building_cmd_tx, driver_cmd_tx) = building.start(); | ||||
|  | ||||
|     tokio::spawn(print_events(events_rx.resubscribe())); | ||||
|     tokio::spawn(driver::driver(events_rx.resubscribe(), driver_cmd_tx)); | ||||
|     tokio::spawn(controller::controller(events_rx, building_cmd_tx)); | ||||
|     building_task.await.unwrap(); | ||||
| } | ||||
|  | ||||
| async fn print_events(mut events_rx: broadcast::Receiver<BuildingEvent>) { | ||||
|     while let Ok(evt) = events_rx.recv().await { | ||||
|         println!("BuildingEvent::{:?}", evt); | ||||
|     } | ||||
| } | ||||
| @@ -8,3 +8,19 @@ | ||||
| {{#include dining-philosophers-async.rs}} | ||||
| ``` | ||||
|  | ||||
| ## Broadcast Chat Application | ||||
|  | ||||
| ([back to exercise](chat-app.md)) | ||||
|  | ||||
| `src/bin/server.rs`: | ||||
|  | ||||
| ```rust,compile_fail | ||||
| {{#include chat-async/src/bin/server.rs}} | ||||
| ``` | ||||
|  | ||||
| `src/bin/client.rs`: | ||||
|  | ||||
| ```rust,compile_fail | ||||
| {{#include chat-async/src/bin/client.rs}} | ||||
| ``` | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user